@@ -522,3 +522,133 @@ def test_validators_use_proper_draft():
522522 }
523523 cc = canonicalish (schema )
524524 jsonschema .validators .validator_for (cc ).check_schema (cc )
525+
526+
527+ # Reference to itself
528+ ROOT_REFERENCE = {"$ref" : "#" }
529+ # One extra nesting level
530+ NESTED = {"not" : {"$ref" : "#/not" }}
531+ # The same as above, but includes "$id".
532+ NESTED_WITH_ID = {
533+ "not" : {"$ref" : "#/not" },
534+ "$id" : "http://json-schema.org/draft-07/schema#" ,
535+ }
536+ SELF_REFERENTIAL = {"foo" : {"$ref" : "#foo" }, "not" : {"$ref" : "#foo" }}
537+
538+
539+ @pytest .mark .parametrize (
540+ "schema, expected" ,
541+ (
542+ (ROOT_REFERENCE , ROOT_REFERENCE ),
543+ (NESTED , NESTED ),
544+ (NESTED_WITH_ID , NESTED_WITH_ID ),
545+ # "foo" content should be inlined as is, because "#" is recursive (special case)
546+ (
547+ {"foo" : {"$ref" : "#" }, "not" : {"$ref" : "#foo" }},
548+ {"foo" : {"$ref" : "#" }, "not" : {"$ref" : "#" }},
549+ ),
550+ # "foo" content should be inlined as is, because it points to itself
551+ (
552+ SELF_REFERENTIAL ,
553+ SELF_REFERENTIAL ,
554+ ),
555+ # The same as above, but with one extra nesting level
556+ (
557+ {"foo" : {"not" : {"$ref" : "#foo" }}, "not" : {"$ref" : "#foo" }},
558+ # 1. We start from resolving "$ref" in "not"
559+ # 2. at this point we don't know this path is recursive, so we follow to "foo"
560+ # 3. inside "foo" we found a reference to "foo", which means it is recursive
561+ {"foo" : {"not" : {"$ref" : "#foo" }}, "not" : {"not" : {"$ref" : "#foo" }}},
562+ ),
563+ # Circular reference between two schemas
564+ (
565+ {"foo" : {"$ref" : "#bar" }, "bar" : {"$ref" : "#foo" }, "not" : {"$ref" : "#foo" }},
566+ # 1. We start in "not" and follow to "foo"
567+ # 2. In "foo" we follow to "bar"
568+ # 3. Here we see a reference to previously seen scope, which means it is a recursive path
569+ # We take the schema where we stop and inline it to the starting point (therefore it is `{"$ref": "#foo"}`)
570+ {"foo" : {"$ref" : "#bar" }, "bar" : {"$ref" : "#foo" }, "not" : {"$ref" : "#foo" }},
571+ ),
572+ ),
573+ )
574+ def test_skip_recursive_references_simple_schemas (schema , expected ):
575+ # When there is a recursive reference, it should not be resolved
576+ assert resolve_all_refs (schema ) == expected
577+
578+
579+ @pytest .mark .parametrize (
580+ "schema, resolved" ,
581+ (
582+ # NOTE. The `resolved` fixture does not include "definitions" to save visual space here, but it is extended
583+ # with it in the test body.
584+ # The reference target is behind two references, that share the same definition path. Not a recursive reference
585+ (
586+ {
587+ "definitions" : {
588+ "properties" : {
589+ "foo" : {"type" : "string" },
590+ "bar" : {"$ref" : "#/definitions/properties/foo" },
591+ },
592+ },
593+ "not" : {"$ref" : "#/definitions/properties/bar" },
594+ },
595+ {
596+ "not" : {"type" : "string" },
597+ },
598+ ),
599+ # Here we need to resolve multiple references while being on the same resolution scope:
600+ # "#/definitions/foo" contains two references
601+ (
602+ {
603+ "definitions" : {
604+ "foo" : {
605+ "properties" : {
606+ "bar" : {"$ref" : "#/definitions/spam" },
607+ "baz" : {"$ref" : "#/definitions/spam" },
608+ }
609+ },
610+ "spam" : {"type" : "string" },
611+ },
612+ "properties" : {"foo" : {"$ref" : "#/definitions/foo" }},
613+ },
614+ {
615+ "properties" : {
616+ "foo" : {
617+ "properties" : {
618+ "bar" : {"type" : "string" },
619+ "baz" : {"type" : "string" },
620+ }
621+ }
622+ },
623+ },
624+ ),
625+ # Similar to the one above, but recursive
626+ (
627+ {
628+ "definitions" : {
629+ "foo" : {
630+ "properties" : {
631+ "bar" : {"$ref" : "#/definitions/spam" },
632+ "baz" : {"$ref" : "#/definitions/spam" },
633+ }
634+ },
635+ "spam" : {"$ref" : "#/definitions/foo" },
636+ },
637+ "properties" : {"foo" : {"$ref" : "#/definitions/foo" }},
638+ },
639+ {
640+ "properties" : {
641+ "foo" : {
642+ "properties" : {
643+ "bar" : {"$ref" : "#/definitions/foo" },
644+ "baz" : {"$ref" : "#/definitions/foo" },
645+ }
646+ }
647+ },
648+ },
649+ ),
650+ ),
651+ )
652+ def test_skip_recursive_references_complex_schemas (schema , resolved ):
653+ resolved ["definitions" ] = schema ["definitions" ]
654+ assert resolve_all_refs (schema ) == resolved
0 commit comments