diff --git a/src/check_jsonschema/schema_loader/main.py b/src/check_jsonschema/schema_loader/main.py index ef808becd..f819b85a8 100644 --- a/src/check_jsonschema/schema_loader/main.py +++ b/src/check_jsonschema/schema_loader/main.py @@ -7,6 +7,7 @@ import urllib.parse import jsonschema +from jsonschema_specifications import REGISTRY as SPECIFICATIONS from referencing import Registry from ..builtin_schemas import get_builtin_schema @@ -164,6 +165,11 @@ def _get_validator( reference_registry = make_reference_registry( self._parsers, retrieval_uri, schema, self.disable_cache ) + reference_resolver = None + if retrieval_uri is not None and not isinstance(schema.get("$id"), str): + reference_resolver = SPECIFICATIONS.combine(reference_registry).resolver( + retrieval_uri + ) if self.validator_class is None: # get the correct validator class and check the schema under its metaschema @@ -195,6 +201,7 @@ def _get_validator( validator = validator_cls( # type: ignore[call-arg] schema, registry=reference_registry, + _resolver=reference_resolver, format_checker=format_checker, ) return t.cast(jsonschema.protocols.Validator, validator) diff --git a/tests/acceptance/test_local_relative_ref.py b/tests/acceptance/test_local_relative_ref.py index f421b69c9..fd7dc340d 100644 --- a/tests/acceptance/test_local_relative_ref.py +++ b/tests/acceptance/test_local_relative_ref.py @@ -29,6 +29,26 @@ CASE2_PASSING_DOCUMENT = {"test": "some data"} CASE2_FAILING_DOCUMENT = {"test": {"foo": "bar"}} +CASE3_MAIN_SCHEMA = { + "type": "object", + "properties": { + "pupils": { + "type": "array", + "items": {"$ref": "../person/person.schema.json"}, + } + }, +} +CASE3_PERSON_SCHEMA = { + "type": "object", + "properties": {"address": {"$ref": "../address/address.schema.json"}}, +} +CASE3_ADDRESS_SCHEMA = { + "type": "object", + "properties": {"zip_code": {"type": "number"}}, +} +CASE3_PASSING_DOCUMENT = {"pupils": [{"address": {"zip_code": 12345}}]} +CASE3_FAILING_DOCUMENT = {"pupils": [{"address": {"zip_code": "invalid"}}]} + def _prep_files(tmp_path, main_schema, other_schema_data, instance): main_schemafile = tmp_path / "main_schema.json" @@ -75,6 +95,34 @@ def test_local_ref_schema( run_line_simple(["--schemafile", schemafile, str(doc)]) +@pytest.mark.parametrize( + "instance, expect_err", + [ + (CASE3_PASSING_DOCUMENT, None), + (CASE3_FAILING_DOCUMENT, "'invalid' is not of type 'number'"), + ], +) +def test_nested_local_ref_schema(run_line, tmp_path, instance, expect_err): + school_dir = tmp_path / "school" + person_dir = tmp_path / "person" + address_dir = tmp_path / "address" + school_dir.mkdir() + person_dir.mkdir() + address_dir.mkdir() + + main_schemafile = school_dir / "school.schema.json" + main_schemafile.write_text(json.dumps(CASE3_MAIN_SCHEMA)) + (person_dir / "person.schema.json").write_text(json.dumps(CASE3_PERSON_SCHEMA)) + (address_dir / "address.schema.json").write_text(json.dumps(CASE3_ADDRESS_SCHEMA)) + doc = school_dir / "school.example.json" + doc.write_text(json.dumps(instance)) + + res = run_line(["check-jsonschema", "--schemafile", str(main_schemafile), str(doc)]) + assert res.exit_code == (1 if expect_err is not None else 0) + if expect_err is not None: + assert expect_err in res.stdout + + @pytest.mark.parametrize( "main_schema, other_schema_data, instance, expect_err", [