From ad8983d9be40483c297cacaa05242124167b612d Mon Sep 17 00:00:00 2001 From: Glen Chatfield Date: Tue, 18 Oct 2022 09:29:27 +0000 Subject: [PATCH 1/4] Tests: Add test for reference as the root object --- tests/schemas/root-ref.json | 1 + tests/test_loader.py | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 tests/schemas/root-ref.json diff --git a/tests/schemas/root-ref.json b/tests/schemas/root-ref.json new file mode 100644 index 0000000..df92cf2 --- /dev/null +++ b/tests/schemas/root-ref.json @@ -0,0 +1 @@ +{"$ref": "#/definitions/bar", "definitions": {"bar": {"type": "integer"}}} diff --git a/tests/test_loader.py b/tests/test_loader.py index e959a64..c9738ff 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -163,3 +163,8 @@ def test_immediate_references_is_detected(): def test_immediate_references_can_be_bypassed(): value = RefDict.from_uri("tests/schemas/immediate-ref.json#/type") assert value == "integer" + + +def test_local_root_ref(): + value = RefDict.from_uri("tests/schemas/root-ref.json#/") + assert value == {"type": "integer"} From 8ccd05a3da131f596ad847d84b0ade5c96e334ab Mon Sep 17 00:00:00 2001 From: Glen Chatfield Date: Tue, 18 Oct 2022 11:38:15 +0000 Subject: [PATCH 2/4] Tests: Add a test for conflicting keys Test that if a JSON object has keys in addition to the `$ref` key that the additional keys are ignored. --- tests/schemas/conflicting-keys.yaml | 5 +++++ tests/test_loader.py | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 tests/schemas/conflicting-keys.yaml diff --git a/tests/schemas/conflicting-keys.yaml b/tests/schemas/conflicting-keys.yaml new file mode 100644 index 0000000..f9d672c --- /dev/null +++ b/tests/schemas/conflicting-keys.yaml @@ -0,0 +1,5 @@ +conflicting: + $ref: "#/real" + value: "bad" +real: + value: "good" diff --git a/tests/test_loader.py b/tests/test_loader.py index c9738ff..693f372 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -168,3 +168,8 @@ def test_immediate_references_can_be_bypassed(): def test_local_root_ref(): value = RefDict.from_uri("tests/schemas/root-ref.json#/") assert value == {"type": "integer"} + + +def test_local_conflicting_keys(): + value = RefDict.from_uri("tests/schemas/conflicting-keys.yaml#/conflicting/value") + assert value == "good" From 0f432c08acaf184c878c410d9db13e892c867579 Mon Sep 17 00:00:00 2001 From: Glen Chatfield Date: Tue, 18 Oct 2022 10:21:57 +0000 Subject: [PATCH 3/4] Support `$ref` to internal members --- json_ref_dict/ref_pointer.py | 44 +++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/json_ref_dict/ref_pointer.py b/json_ref_dict/ref_pointer.py index 6e6bba8..4dcd70b 100644 --- a/json_ref_dict/ref_pointer.py +++ b/json_ref_dict/ref_pointer.py @@ -47,10 +47,47 @@ def resolve_remote_with_uri( :return: tuple indicating (1) if doc was a ref (the ref URI returned) and (2) what that ref value was. """ - if not ( - isinstance(doc, abc.Mapping) and isinstance(doc.get("$ref"), str) - ): + + is_ref = isinstance(doc, abc.Mapping) and isinstance( + doc.get("$ref"), str + ) + + if not is_ref: return None, None + + ref = doc["$ref"] + + is_local = ref.startswith("#") + + if is_local: + # If this reference is local to this document, check the following + # to determine if this reference is actually pointing to within + # an object containing a `$ref` which is currently being resolved: + # + # 1) If we are already resolving this reference (i.e, `self` is the + # same as the reference we just found) + # 2) This reference doesn't just point to itself (i.e, there are + # more parts to resolve) + # + # If both these things are true, ignore this `$ref`, and allow the + # existing resolve process (represented by `self`) to continue. + + # If a URI ended in `/`, there will be an empty item on the end of + # `parts`. For our purposes, this is the same as if it were not + # present. Modify the ephemeral `ref_pointer` `parts` to resolve + # this discrepancy + ref_pointer = RefPointer(ref) + if self.parts[-1] == "" and ref_pointer.parts[-1] != "": + ref_pointer.parts.append("") + + is_same = self.parts == ref_pointer.parts + has_more = parts_idx + 1 < len(self.parts) + + if is_same and has_more: + # `$ref` points to within the JSON object represented by `self`. + # Ignore. + return None, None + remote_uri = self.uri.relative(doc["$ref"]).get( *[parse_segment(part) for part in self.parts[parts_idx + 1 :]] ) @@ -113,6 +150,7 @@ def resolve_with_uri( if default is _nothing: raise return self.uri, default + return self.uri, doc def set(self, doc: Any, value: Any, inplace: bool = True) -> NoReturn: From 2474d3e366a3529ddeaecd2985853ba0a3159657 Mon Sep 17 00:00:00 2001 From: Glen Chatfield Date: Tue, 18 Oct 2022 10:46:51 +0000 Subject: [PATCH 4/4] Changelog: Fixed references pointing within object Fixed references which point to within the object containing the `$ref` key. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e0180c..1bb20a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Types of changes are: ## [Unreleased] +* Added support for references which point to within the object containing the + `$ref` key. + ## [0.7.1] - 2021-09-10 ### Fixed