Skip to content
Open
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
7 changes: 7 additions & 0 deletions src/check_jsonschema/schema_loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the call to SPECIFICATIONS.combine() do here? I was just experimenting with a patch using reference_registry.resolver(), which also seems to work.
It may be correct, but I'd like to know why before proceeding.

Also, if we are adding direct use of jsonschema_specifications, it should be added as a direct dependency, even if the version is not bounded.


if self.validator_class is None:
# get the correct validator class and check the schema under its metaschema
Expand Down Expand Up @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions tests/acceptance/test_local_relative_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
[
Expand Down
Loading