diff --git a/.gitignore b/.gitignore index 31236db57..f3e0c86f8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ _static _templates TODO +.idea +.python-version +.eggs +jsonschema.egg-info diff --git a/jsonschema/tests/fixtures/__init__.py b/jsonschema/tests/fixtures/__init__.py new file mode 100644 index 000000000..8649d7926 --- /dev/null +++ b/jsonschema/tests/fixtures/__init__.py @@ -0,0 +1,6 @@ +from pathlib import Path +from json import loads + +apple = loads(open(str(Path(__file__).parents[0]) + '/apple.json').read()) +orange = loads(open(str(Path(__file__).parents[0]) + '/orange.json').read()) +tree = loads(open(str(Path(__file__).parents[0]) + '/tree.json').read()) diff --git a/jsonschema/tests/fixtures/apple.json b/jsonschema/tests/fixtures/apple.json new file mode 100644 index 000000000..f06fd1210 --- /dev/null +++ b/jsonschema/tests/fixtures/apple.json @@ -0,0 +1,19 @@ +{ + "$id": "apples", + "type": "object", + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/jsonschema/tests/fixtures/orange.json b/jsonschema/tests/fixtures/orange.json new file mode 100644 index 000000000..7d6d2892c --- /dev/null +++ b/jsonschema/tests/fixtures/orange.json @@ -0,0 +1,19 @@ +{ + "$id": "oranges", + "type": "object", + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/jsonschema/tests/fixtures/tree.json b/jsonschema/tests/fixtures/tree.json new file mode 100644 index 000000000..20997ebda --- /dev/null +++ b/jsonschema/tests/fixtures/tree.json @@ -0,0 +1,21 @@ +{ + "$id": "tree", + "type": "object", + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "name": { + "type": "string" + }, + "fruits": { + "type": "object", + "properties": { + "apples": { + "$ref": "apples" + }, + "oranges": { + "$ref": "oranges" + } + } + } + } +} \ No newline at end of file diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 0adbf3060..4af370789 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -12,6 +12,13 @@ from twisted.trial.unittest import SynchronousTestCase import attr +from jsonschema import ( + SchemaError, + ValidationError, + _types, +) +from jsonschema.tests.compat import mock +from jsonschema.tests.fixtures import apple, orange, tree from jsonschema import FormatChecker, TypeChecker, exceptions, validators from jsonschema.compat import PY3, pathname2url import jsonschema @@ -1755,6 +1762,19 @@ def test_helpful_error_message_on_failed_pop_scope(self): resolver.pop_scope() self.assertIn("Failed to pop the scope", str(exc.exception)) + def test_export_resolved_references(self): + ref_resolver = validators.RefResolver( + '', + None, + store={ + 'apples': apple, + 'oranges': orange, + }) + resolved_tree_refs = ref_resolver.export_resolved_references(tree) + self.assertTrue(apple, resolved_tree_refs['properties']['fruits']['properties']['apples']) + self.assertTrue(orange, resolved_tree_refs['properties']['fruits']['properties']['oranges']) + + def sorted_errors(errors): def key(error): diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 7b7d76dfb..9d22583d4 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -6,6 +6,7 @@ import numbers from six import add_metaclass +from urllib.parse import urlparse from jsonschema import ( _legacy_validators, @@ -832,6 +833,33 @@ def resolve_remote(self, uri): self.store[uri] = result return result + def export_resolved_references(self, schema: dict): + """ + Resolves json references and merges them into a consolidated schema for validation purposes + :param schema: + :return: schema merged with resolved references + """ + if len(self.store) <= 2: + return RefResolutionError('RefResolver does not have any additional ' +\ + 'referenced schemas outside of draft 3 & 4') + + if isinstance(schema, dict): + for key, value in schema.items(): + if key == "$ref": + ref_schema = self.resolve(urlparse(value).path) + if ref_schema: + return ref_schema[1] + + resolved_ref = self.export_resolved_references(value) + if resolved_ref: + schema[key] = resolved_ref + elif isinstance(schema, list): + for (idx, value) in enumerate(schema): + resolved_ref = self.export_resolved_references(value) + if resolved_ref: + schema[idx] = resolved_ref + return schema + def validate(instance, schema, cls=None, *args, **kwargs): """