diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-27 08:58:01 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-27 09:09:44 +0900 |
| commit | 2e4a3cf59df1bc85b0f626f598a5d45bcae8d77a (patch) | |
| tree | f125fb944ad57b1819447eb61b5adfa687f25e34 | |
| parent | 901878ee3f2bee6605b02d321cc4c92bc32fd5b0 (diff) | |
| download | php-shirabe-2e4a3cf59df1bc85b0f626f598a5d45bcae8d77a.tar.gz php-shirabe-2e4a3cf59df1bc85b0f626f598a5d45bcae8d77a.tar.zst php-shirabe-2e4a3cf59df1bc85b0f626f598a5d45bcae8d77a.zip | |
fix(json): include property name in required-property errors
The jsonschema crate reports a missing required property against its
parent object, leaving the instance path empty at the root. This skipped
the "PROPERTY : MESSAGE" formatting that Composer produces. Append the
missing property name to the path so the output shape matches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| -rw-r--r-- | crates/shirabe/src/json/json_file.rs | 16 | ||||
| -rw-r--r-- | crates/shirabe/tests/command/validate_command_test.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/tests/json/json_file_test.rs | 22 |
3 files changed, 31 insertions, 16 deletions
diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index 27540fe..b38de1e 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -404,11 +404,25 @@ impl JsonFile { let errors: Vec<String> = validator .iter_errors(&data_value) .map(|error| { - let property = error + let mut property = error .instance_path() .as_str() .trim_start_matches('/') .replace('/', "."); + // A missing required property is reported against its parent object, so the + // instance path is the parent (empty at the root). Composer points the error at + // the missing property itself, so append its name to match the `PROPERTY : MESSAGE` + // shape. + if let jsonschema::error::ValidationErrorKind::Required { property: missing } = + error.kind() + && let Some(name) = missing.as_str() + { + if property.is_empty() { + property = name.to_string(); + } else { + property = format!("{}.{}", property, name); + } + } if property.is_empty() { error.to_string() } else { diff --git a/crates/shirabe/tests/command/validate_command_test.rs b/crates/shirabe/tests/command/validate_command_test.rs index 933a0ff..c40f66b 100644 --- a/crates/shirabe/tests/command/validate_command_test.rs +++ b/crates/shirabe/tests/command/validate_command_test.rs @@ -74,13 +74,14 @@ fn provide_validate_tests() -> Vec<ValidateCase> { ValidateCase { // WORDING NOTE: upstream asserts justinrainbow's property-prefixed strings // "- name : The property name is required" / "- description : The property description - // is required". The jsonschema crate reports the same required violations as - // "\"name\" is a required property" (no property prefix; the property name is embedded - // in the message). Matches the divergence already accepted in json_file_test. + // is required". The jsonschema crate phrases the message differently, but the property + // prefix is reconstructed, so the shape matches: + // "- name : \"name\" is a required property". Matches the divergence already accepted in + // json_file_test. name: "passing but with warnings", composer_json: publish_data_stripped.clone(), command: vec![], - expected: "./composer.json is valid for simple usage with Composer but has\nstrict errors that make it unable to be published as a package\n<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>\n# Publish errors\n- \"name\" is a required property\n- \"description\" is a required property\n<warning># General warnings</warning>\n- No license specified, it is recommended to do so. For closed-source software you may use \"proprietary\" as license.", + expected: "./composer.json is valid for simple usage with Composer but has\nstrict errors that make it unable to be published as a package\n<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>\n# Publish errors\n- name : \"name\" is a required property\n- description : \"description\" is a required property\n<warning># General warnings</warning>\n- No license specified, it is recommended to do so. For closed-source software you may use \"proprietary\" as license.", }, ValidateCase { name: "passing without publish-check", diff --git a/crates/shirabe/tests/json/json_file_test.rs b/crates/shirabe/tests/json/json_file_test.rs index cd4445c..3b56d35 100644 --- a/crates/shirabe/tests/json/json_file_test.rs +++ b/crates/shirabe/tests/json/json_file_test.rs @@ -304,9 +304,9 @@ fn test_schema_validation_lax_additional_properties() { #[test] fn test_schema_validation_lax_required() { // WORDING NOTE: upstream asserts justinrainbow's property-prefixed strings such as - // "name : The property name is required". The jsonschema crate reports the same required - // violations as "\"name\" is a required property" (no property prefix; the property name is - // embedded in the message). + // "name : The property name is required". The jsonschema crate phrases the message + // differently ("name : \"name\" is a required property"), but the property prefix is reconstructed, + // so the "PROPERTY : MESSAGE" shape matches: "name : \"name\" is a required property". let file_tmp = create_temp_file(); let file = file_tmp.path().to_str().unwrap().to_string(); let json = JsonFile::new(file.clone(), None, None).unwrap(); @@ -320,8 +320,8 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); - assert!(errors.contains(&"\"name\" is a required property".to_string())); - assert!(errors.contains(&"\"description\" is a required property".to_string())); + assert!(errors.contains(&"name : \"name\" is a required property".to_string())); + assert!(errors.contains(&"description : \"description\" is a required property".to_string())); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); std::fs::write(&file, b"{ \"name\": \"vendor/package\" }").unwrap(); @@ -331,7 +331,7 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!( - &vec!["\"description\" is a required property".to_string()], + &vec!["description : \"description\" is a required property".to_string()], e.get_errors() ); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); @@ -343,7 +343,7 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!( - &vec!["\"name\" is a required property".to_string()], + &vec!["name : \"name\" is a required property".to_string()], e.get_errors() ); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); @@ -355,8 +355,8 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); - assert!(errors.contains(&"\"name\" is a required property".to_string())); - assert!(errors.contains(&"\"description\" is a required property".to_string())); + assert!(errors.contains(&"name : \"name\" is a required property".to_string())); + assert!(errors.contains(&"description : \"description\" is a required property".to_string())); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); std::fs::write(&file, b"{ \"type\": \"project\" }").unwrap(); @@ -366,8 +366,8 @@ fn test_schema_validation_lax_required() { let e = err.downcast_ref::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); - assert!(errors.contains(&"\"name\" is a required property".to_string())); - assert!(errors.contains(&"\"description\" is a required property".to_string())); + assert!(errors.contains(&"name : \"name\" is a required property".to_string())); + assert!(errors.contains(&"description : \"description\" is a required property".to_string())); json.validate_schema(JsonFile::LAX_SCHEMA, None).unwrap(); std::fs::write( |
