Skip to content

Commit eff59e8

Browse files
committed
feat(cli): Schema-only validation now also validates all referenced schemas
Signed-off-by: Dmitry Dygalo <[email protected]>
1 parent 93ce470 commit eff59e8

File tree

3 files changed

+150
-2
lines changed

3 files changed

+150
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- `evaluate()` top-level function for convenient access to structured validation output.
8+
- **CLI**: Schema-only validation now also validates all referenced schemas. [#804](https://github.com/Stranger6667/jsonschema/issues/804)
89

910
### Changed
1011

crates/jsonschema-cli/src/main.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,27 @@ fn output_schema_validation(
187187
output: Output,
188188
errors_only: bool,
189189
) -> Result<bool, Box<dyn std::error::Error>> {
190+
// First validate against meta-schema
190191
let meta_validator = jsonschema::meta::validator_for(schema_json)?;
191192
let evaluation = meta_validator.evaluate(schema_json);
192193
let flag_output = evaluation.flag();
193194

195+
// If meta-schema validation passed, also try to build the validator
196+
// to check that all referenced schemas are valid
197+
if flag_output.valid {
198+
let base_uri = path_to_uri(schema_path);
199+
let base_uri = referencing::uri::from_str(&base_uri)?;
200+
// Just try to build - if it fails, the error propagates naturally
201+
jsonschema::options()
202+
.with_base_uri(base_uri)
203+
.build(schema_json)?;
204+
}
205+
194206
// Skip valid schemas if errors_only is enabled
195207
if !(errors_only && flag_output.valid) {
196208
let schema_display = schema_path.to_string_lossy().to_string();
197209
let output_format = output.as_str();
210+
198211
let payload = match output {
199212
Output::Text => unreachable!("text mode should not call this function"),
200213
Output::Flag => serde_json::to_value(flag_output)?,
@@ -222,8 +235,20 @@ fn validate_schema_meta(
222235

223236
if matches!(output, Output::Text) {
224237
// Text output mode
225-
match jsonschema::meta::validate(&schema_json) {
226-
Ok(()) => {
238+
// First validate the schema structure against its meta-schema
239+
if let Err(error) = jsonschema::meta::validate(&schema_json) {
240+
println!("Schema is invalid. Error: {error}");
241+
return Ok(false);
242+
}
243+
244+
// Then try to build a validator to check that all referenced schemas are also valid
245+
let base_uri = path_to_uri(schema_path);
246+
let base_uri = referencing::uri::from_str(&base_uri)?;
247+
match jsonschema::options()
248+
.with_base_uri(base_uri)
249+
.build(&schema_json)
250+
{
251+
Ok(_) => {
227252
if !errors_only {
228253
println!("Schema is valid");
229254
}

crates/jsonschema-cli/tests/cli.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,3 +884,125 @@ fn test_validate_schema_with_json_parse_error() {
884884
let stdout = String::from_utf8_lossy(&output.stdout);
885885
assert!(stdout.contains("Error:"));
886886
}
887+
888+
#[test]
889+
fn test_validate_schema_with_invalid_referenced_schema() {
890+
// This test verifies that when a schema references another schema via $ref,
891+
// and that referenced schema is invalid, the validation should fail.
892+
let dir = tempdir().unwrap();
893+
894+
// Main schema is structurally valid
895+
let main_schema = create_temp_file(
896+
&dir,
897+
"main.json",
898+
r#"{
899+
"$schema": "http://json-schema.org/draft-07/schema#",
900+
"type": "object",
901+
"properties": {
902+
"user": { "$ref": "user.json" }
903+
}
904+
}"#,
905+
);
906+
907+
// Referenced schema is structurally INVALID (bad type value)
908+
let _ref_schema = create_temp_file(
909+
&dir,
910+
"user.json",
911+
r#"{
912+
"type": "invalid_type_here",
913+
"properties": {
914+
"name": { "type": "string" }
915+
}
916+
}"#,
917+
);
918+
919+
let mut cmd = cli();
920+
cmd.arg(&main_schema);
921+
let output = cmd.output().unwrap();
922+
923+
// Schema validation should fail because the referenced schema is invalid
924+
assert!(!output.status.success());
925+
let stdout = String::from_utf8_lossy(&output.stdout);
926+
assert!(stdout.contains("Schema is invalid"));
927+
}
928+
929+
#[test]
930+
fn test_validate_schema_with_valid_referenced_schema() {
931+
// This test verifies that when all referenced schemas are valid, validation succeeds.
932+
let dir = tempdir().unwrap();
933+
934+
let main_schema = create_temp_file(
935+
&dir,
936+
"main.json",
937+
r#"{
938+
"$schema": "http://json-schema.org/draft-07/schema#",
939+
"type": "object",
940+
"properties": {
941+
"user": { "$ref": "user.json" }
942+
}
943+
}"#,
944+
);
945+
946+
// Referenced schema is structurally VALID
947+
let _ref_schema = create_temp_file(
948+
&dir,
949+
"user.json",
950+
r#"{
951+
"type": "object",
952+
"properties": {
953+
"name": { "type": "string" }
954+
}
955+
}"#,
956+
);
957+
958+
let mut cmd = cli();
959+
cmd.arg(&main_schema);
960+
let output = cmd.output().unwrap();
961+
962+
// Schema validation should succeed because all schemas are valid
963+
assert!(output.status.success());
964+
let stdout = String::from_utf8_lossy(&output.stdout);
965+
assert!(stdout.contains("Schema is valid"));
966+
}
967+
968+
#[test]
969+
fn test_validate_schema_with_invalid_ref_structured_output() {
970+
// This test verifies structured output when root schema is valid but referenced schema is invalid.
971+
// This exercises the code path where flag_output.valid is true, but build fails.
972+
let dir = tempdir().unwrap();
973+
974+
let main_schema = create_temp_file(
975+
&dir,
976+
"main.json",
977+
r#"{
978+
"$schema": "http://json-schema.org/draft-07/schema#",
979+
"type": "object",
980+
"properties": {
981+
"user": { "$ref": "user.json" }
982+
}
983+
}"#,
984+
);
985+
986+
// Referenced schema is structurally INVALID
987+
let _ref_schema = create_temp_file(
988+
&dir,
989+
"user.json",
990+
r#"{
991+
"type": "invalid_type_here",
992+
"properties": {
993+
"name": { "type": "string" }
994+
}
995+
}"#,
996+
);
997+
998+
let mut cmd = cli();
999+
cmd.arg(&main_schema).arg("--output").arg("flag");
1000+
let output = cmd.output().unwrap();
1001+
1002+
// Should fail
1003+
assert!(!output.status.success());
1004+
1005+
// Should get an error message (not structured output since build fails before we can output)
1006+
let stdout = String::from_utf8_lossy(&output.stdout);
1007+
assert!(stdout.contains("Error:"));
1008+
}

0 commit comments

Comments
 (0)