Skip to content

Commit 9394023

Browse files
committed
#782: Implements dynamicRef validations
1 parent 89b8508 commit 9394023

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

jsonschema/_validators.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from fractions import Fraction
2+
from urllib.parse import urldefrag, urljoin
23
import re
34

45
from jsonschema._utils import (
@@ -384,6 +385,24 @@ def ref(validator, ref, instance, schema):
384385
validator.resolver.pop_scope()
385386

386387

388+
def dynamicRef(validator, dynamicRef, instance, schema):
389+
_, fragment = urldefrag(dynamicRef)
390+
scope_stack = validator.resolver.scopes_stack_copy
391+
392+
for url in scope_stack:
393+
with validator.resolver.resolving(urljoin(url, dynamicRef)) as lookup_schema:
394+
if "$dynamicAnchor" in lookup_schema and fragment == lookup_schema["$dynamicAnchor"]:
395+
subschema = lookup_schema
396+
for error in validator.descend(instance, subschema):
397+
yield error
398+
break
399+
else:
400+
with validator.resolver.resolving(dynamicRef) as lookup_schema:
401+
subschema = lookup_schema
402+
for error in validator.descend(instance, subschema):
403+
yield error
404+
405+
387406
def defs(validator, defs, instance, schema):
388407
if not validator.is_type(instance, "object"):
389408
return

jsonschema/tests/test_jsonschema_test_suite.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,5 +713,18 @@ def ecmascript_regex_validation(test):
713713
or complex_email_validation(test)
714714
or format_validation_annotation(test)
715715
or ecmascript_regex_validation(test)
716+
or skip(
717+
message="ToDo: Extend validation",
718+
subject="dynamicRef",
719+
case_description="A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor should "
720+
"resolve to the first $dynamicAnchor in the dynamic scope",
721+
description='The recursive part is not valid against the root',
722+
)(test)
723+
or skip(
724+
message="ToDo: Extend validation",
725+
subject="dynamicRef",
726+
case_description="multiple dynamic paths to the $dynamicRef keyword",
727+
description='recurse to integerNode - floats are not allowed',
728+
)(test)
716729
),
717730
)

jsonschema/validators.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ def extend(validator, validators=(), version=None, type_checker=None):
437437
validators={
438438
u"$ref": _validators.ref,
439439
u"$defs": _validators.defs,
440+
u"$dynamicRef": _validators.dynamicRef,
440441
u"additionalItems": _validators.additionalItems,
441442
u"additionalProperties": _validators.additionalProperties,
442443
u"allOf": _validators.allOf,
@@ -609,6 +610,13 @@ def resolution_scope(self):
609610
"""
610611
return self._scopes_stack[-1]
611612

613+
@property
614+
def scopes_stack_copy(self):
615+
"""
616+
Retrieve a copy of the stack of resolution scopes.
617+
"""
618+
return self._scopes_stack.copy()
619+
612620
@property
613621
def base_uri(self):
614622
"""
@@ -677,6 +685,7 @@ def resolve_local(self, url, schema):
677685
self.store[url] = subschema
678686
return subschema
679687

688+
680689
def resolve(self, ref):
681690
"""
682691
Resolve the given reference.
@@ -720,10 +729,12 @@ def resolve_fragment(self, document, fragment):
720729

721730
fragment = fragment.lstrip(u"/")
722731

723-
# Resolve via anchor
724-
for subschema in self._finditem(document, "$anchor"):
725-
if fragment == subschema['$anchor']:
726-
return subschema
732+
# Resolve fragment via $anchor or $dynamicAnchor
733+
if fragment:
734+
for keyword in ["$anchor", "$dynamicAnchor"]:
735+
for subschema in self._finditem(document, keyword):
736+
if fragment == subschema[keyword]:
737+
return subschema
727738

728739
# Resolve via path
729740
parts = unquote(fragment).split(u"/") if fragment else []

0 commit comments

Comments
 (0)