Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions validation/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,52 @@ def test_yaml_diagnostics_name_the_file_and_show_the_line(self):
if has_snippet:
self.assertIn("^", result.stdout)

def test_schema_that_is_not_an_object_is_reported(self):
"""A schema may be an object or a boolean; any other JSON value is rejected."""
for schema_text in ("[]", '"a string"', "3", "null", "[{}]"):
with self.subTest(schema=schema_text):
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "model.yaml"
model_path.write_text(_MINIMAL_MODEL)
schema_path = Path(temp_dir) / "schema.json"
schema_path.write_text(schema_text)
result = self.run_validator_args(model_path, "--schema", schema_path)

self.assertEqual(result.returncode, 1)
self.assertIn("Error: Invalid schema", result.stdout)
self.assertIn("is not of type 'object', 'boolean'", result.stdout)
self.assertNotIn("Traceback", result.stderr)

def test_schema_with_a_malformed_keyword_is_reported(self):
"""`required` taking a string used to make the validator demand its characters."""
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "model.yaml"
model_path.write_text(_MINIMAL_MODEL)
schema_path = Path(temp_dir) / "schema.json"
schema_path.write_text('{"type": "object", "required": "name"}')
result = self.run_validator_args(model_path, "--schema", schema_path)

self.assertEqual(result.returncode, 1)
self.assertIn("Error: Invalid schema", result.stdout)
self.assertIn("required", result.stdout)
self.assertNotIn("'a' is a required property", result.stdout)
self.assertNotIn("Traceback", result.stderr)

def test_boolean_schemas_are_still_accepted(self):
"""`true` and `false` are legal schemas, so the new check must let them through."""
for schema_text, expected_code in (("true", 0), ("false", 1)):
with self.subTest(schema=schema_text):
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "model.yaml"
model_path.write_text(_MINIMAL_MODEL)
schema_path = Path(temp_dir) / "schema.json"
schema_path.write_text(schema_text)
result = self.run_validator_args(model_path, "--schema", schema_path)

self.assertEqual(result.returncode, expected_code)
self.assertNotIn("Error: Invalid schema", result.stdout)
self.assertNotIn("Traceback", result.stderr)


if __name__ == "__main__":
unittest.main()
14 changes: 14 additions & 0 deletions validation/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
try:
import yaml
from jsonschema import Draft202012Validator
from jsonschema.exceptions import SchemaError
from referencing import Registry
from referencing.exceptions import NoSuchResource, Unresolvable
from referencing.retrieval import to_cached_resource
Expand Down Expand Up @@ -628,6 +629,19 @@ def main():
print(f"Error: Invalid JSON in schema {schema_path}: {e}")
sys.exit(1)

# json.loads accepts any JSON value, but a schema has to be an object or a
# boolean. Handing the validator anything else raises AttributeError from inside
# referencing, which names neither the schema nor the file it came from. Checking
# against the metaschema also catches an object whose keywords are the wrong
# shape, such as a string "required", which otherwise validates the model against
# nonsense without complaint.
try:
Draft202012Validator.check_schema(schema)
except SchemaError as e:
location = " -> ".join(str(part) for part in e.absolute_path) if e.absolute_path else "(root)"
print(f"Error: Invalid schema {schema_path}: {location}: {e.message}")
sys.exit(1)

model_text = read_text_or_exit(yaml_path, str(yaml_path))
try:
data = load_yaml_named(model_text, str(yaml_path))
Expand Down
Loading