@@ -558,3 +558,133 @@ def test_validators_use_proper_draft():
558
558
}
559
559
cc = canonicalish (schema )
560
560
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