@@ -558,3 +558,133 @@ def test_validators_use_proper_draft():
558558 }
559559 cc = canonicalish (schema )
560560 jsonschema .validators .validator_for (cc ).check_schema (cc )
561+
562+
563+ # Reference to itself
564+ ROOT_REFERENCE = {"$ref" : "#" }
565+ # One extra nesting level
566+ NESTED = {"not" : {"$ref" : "#/not" }}
567+ # The same as above, but includes "$id".
568+ NESTED_WITH_ID = {
569+ "not" : {"$ref" : "#/not" },
570+ "$id" : "http://json-schema.org/draft-07/schema#" ,
571+ }
572+ SELF_REFERENTIAL = {"foo" : {"$ref" : "#foo" }, "not" : {"$ref" : "#foo" }}
573+
574+
575+ @pytest .mark .parametrize (
576+ "schema, expected" ,
577+ (
578+ (ROOT_REFERENCE , ROOT_REFERENCE ),
579+ (NESTED , NESTED ),
580+ (NESTED_WITH_ID , NESTED_WITH_ID ),
581+ # "foo" content should be inlined as is, because "#" is recursive (special case)
582+ (
583+ {"foo" : {"$ref" : "#" }, "not" : {"$ref" : "#foo" }},
584+ {"foo" : {"$ref" : "#" }, "not" : {"$ref" : "#" }},
585+ ),
586+ # "foo" content should be inlined as is, because it points to itself
587+ (
588+ SELF_REFERENTIAL ,
589+ SELF_REFERENTIAL ,
590+ ),
591+ # The same as above, but with one extra nesting level
592+ (
593+ {"foo" : {"not" : {"$ref" : "#foo" }}, "not" : {"$ref" : "#foo" }},
594+ # 1. We start from resolving "$ref" in "not"
595+ # 2. at this point we don't know this path is recursive, so we follow to "foo"
596+ # 3. inside "foo" we found a reference to "foo", which means it is recursive
597+ {"foo" : {"not" : {"$ref" : "#foo" }}, "not" : {"not" : {"$ref" : "#foo" }}},
598+ ),
599+ # Circular reference between two schemas
600+ (
601+ {"foo" : {"$ref" : "#bar" }, "bar" : {"$ref" : "#foo" }, "not" : {"$ref" : "#foo" }},
602+ # 1. We start in "not" and follow to "foo"
603+ # 2. In "foo" we follow to "bar"
604+ # 3. Here we see a reference to previously seen scope, which means it is a recursive path
605+ # We take the schema where we stop and inline it to the starting point (therefore it is `{"$ref": "#foo"}`)
606+ {"foo" : {"$ref" : "#bar" }, "bar" : {"$ref" : "#foo" }, "not" : {"$ref" : "#foo" }},
607+ ),
608+ ),
609+ )
610+ def test_skip_recursive_references_simple_schemas (schema , expected ):
611+ # When there is a recursive reference, it should not be resolved
612+ assert resolve_all_refs (schema ) == expected
613+
614+
615+ @pytest .mark .parametrize (
616+ "schema, resolved" ,
617+ (
618+ # NOTE. The `resolved` fixture does not include "definitions" to save visual space here, but it is extended
619+ # with it in the test body.
620+ # The reference target is behind two references, that share the same definition path. Not a recursive reference
621+ (
622+ {
623+ "definitions" : {
624+ "properties" : {
625+ "foo" : {"type" : "string" },
626+ "bar" : {"$ref" : "#/definitions/properties/foo" },
627+ },
628+ },
629+ "not" : {"$ref" : "#/definitions/properties/bar" },
630+ },
631+ {
632+ "not" : {"type" : "string" },
633+ },
634+ ),
635+ # Here we need to resolve multiple references while being on the same resolution scope:
636+ # "#/definitions/foo" contains two references
637+ (
638+ {
639+ "definitions" : {
640+ "foo" : {
641+ "properties" : {
642+ "bar" : {"$ref" : "#/definitions/spam" },
643+ "baz" : {"$ref" : "#/definitions/spam" },
644+ }
645+ },
646+ "spam" : {"type" : "string" },
647+ },
648+ "properties" : {"foo" : {"$ref" : "#/definitions/foo" }},
649+ },
650+ {
651+ "properties" : {
652+ "foo" : {
653+ "properties" : {
654+ "bar" : {"type" : "string" },
655+ "baz" : {"type" : "string" },
656+ }
657+ }
658+ },
659+ },
660+ ),
661+ # Similar to the one above, but recursive
662+ (
663+ {
664+ "definitions" : {
665+ "foo" : {
666+ "properties" : {
667+ "bar" : {"$ref" : "#/definitions/spam" },
668+ "baz" : {"$ref" : "#/definitions/spam" },
669+ }
670+ },
671+ "spam" : {"$ref" : "#/definitions/foo" },
672+ },
673+ "properties" : {"foo" : {"$ref" : "#/definitions/foo" }},
674+ },
675+ {
676+ "properties" : {
677+ "foo" : {
678+ "properties" : {
679+ "bar" : {"$ref" : "#/definitions/foo" },
680+ "baz" : {"$ref" : "#/definitions/foo" },
681+ }
682+ }
683+ },
684+ },
685+ ),
686+ ),
687+ )
688+ def test_skip_recursive_references_complex_schemas (schema , resolved ):
689+ resolved ["definitions" ] = schema ["definitions" ]
690+ assert resolve_all_refs (schema ) == resolved
0 commit comments