From b608b8810636eaf124c72aebcbd542297b6956a2 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 8 May 2026 15:04:07 -0700 Subject: [PATCH 1/3] [spr] initial version Created using spr 1.3.6-beta.1 --- tests/cases/cycle-detection/base.json | 216 ++++++++++++++++++ .../add-property-to-mutual-recursion.out | 33 +++ .../output/change-item-value-type.out | 0 .../change-mutual-recursion-property.out | 32 +++ .../output/change-three-cycle-property.out | 32 +++ .../output/change-wrapped-cycle-property.out | 32 +++ .../add-property-to-mutual-recursion.json | 9 + .../patch/change-item-value-type.json | 0 .../change-mutual-recursion-property.json | 7 + .../patch/change-three-cycle-property.json | 7 + .../patch/change-wrapped-cycle-property.json | 7 + tests/cases/cycle-shared-prefix/base.json | 54 ----- 12 files changed, 375 insertions(+), 54 deletions(-) create mode 100644 tests/cases/cycle-detection/base.json create mode 100644 tests/cases/cycle-detection/output/add-property-to-mutual-recursion.out rename tests/cases/{cycle-shared-prefix => cycle-detection}/output/change-item-value-type.out (100%) create mode 100644 tests/cases/cycle-detection/output/change-mutual-recursion-property.out create mode 100644 tests/cases/cycle-detection/output/change-three-cycle-property.out create mode 100644 tests/cases/cycle-detection/output/change-wrapped-cycle-property.out create mode 100644 tests/cases/cycle-detection/patch/add-property-to-mutual-recursion.json rename tests/cases/{cycle-shared-prefix => cycle-detection}/patch/change-item-value-type.json (100%) create mode 100644 tests/cases/cycle-detection/patch/change-mutual-recursion-property.json create mode 100644 tests/cases/cycle-detection/patch/change-three-cycle-property.json create mode 100644 tests/cases/cycle-detection/patch/change-wrapped-cycle-property.json delete mode 100644 tests/cases/cycle-shared-prefix/base.json diff --git a/tests/cases/cycle-detection/base.json b/tests/cases/cycle-detection/base.json new file mode 100644 index 0000000..131d54f --- /dev/null +++ b/tests/cases/cycle-detection/base.json @@ -0,0 +1,216 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Cycle detection test fixture", + "version": "1.0.0", + "description": "Exercises cycle detection across a variety of schema shapes." + }, + "paths": { + "/items": { + "get": { + "operationId": "list_items", + "responses": { + "200": { + "description": "A page of items", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ItemPage" + } + } + } + } + } + } + }, + "/persons": { + "get": { + "operationId": "list_persons", + "responses": { + "200": { + "description": "Persons; reaches Company through a mutual reference", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Person" + } + } + } + } + } + } + }, + "/companies": { + "get": { + "operationId": "list_companies", + "responses": { + "200": { + "description": "Companies; reaches Person through a mutual reference", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Company" + } + } + } + } + } + } + }, + "/three-cycle": { + "get": { + "operationId": "get_three_cycle", + "responses": { + "200": { + "description": "Three-way cycle: NodeA -> NodeB -> NodeC -> NodeA", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NodeA" + } + } + } + } + } + } + }, + "/wrapped-cycle": { + "get": { + "operationId": "get_wrapped_cycle", + "responses": { + "200": { + "description": "Mutual recursion routed through allOf wrappers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Wrapper" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ItemPage": { + "description": "A page of items", + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + } + } + }, + "required": ["items"] + }, + "Item": { + "description": "An item", + "type": "object", + "properties": { + "value": { + "type": "string" + } + }, + "required": ["value"] + }, + "Person": { + "description": "A person who works at a company.", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "employer": { + "$ref": "#/components/schemas/Company" + } + }, + "required": ["name"] + }, + "Company": { + "description": "A company with a CEO who is a person.", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "ceo": { + "$ref": "#/components/schemas/Person" + } + }, + "required": ["name"] + }, + "NodeA": { + "description": "First node of a three-way cycle.", + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "next": { + "$ref": "#/components/schemas/NodeB" + } + } + }, + "NodeB": { + "description": "Second node of a three-way cycle.", + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "next": { + "$ref": "#/components/schemas/NodeC" + } + } + }, + "NodeC": { + "description": "Third node of a three-way cycle.", + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "next": { + "$ref": "#/components/schemas/NodeA" + } + } + }, + "Wrapper": { + "description": "Cycles back to Wrapped through an allOf indirection.", + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "child": { + "allOf": [ + { + "$ref": "#/components/schemas/Wrapped" + } + ] + } + } + }, + "Wrapped": { + "description": "Cycles back to Wrapper through an allOf indirection.", + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "back": { + "allOf": [ + { + "$ref": "#/components/schemas/Wrapper" + } + ] + } + } + } + } + } +} diff --git a/tests/cases/cycle-detection/output/add-property-to-mutual-recursion.out b/tests/cases/cycle-detection/output/add-property-to-mutual-recursion.out new file mode 100644 index 0000000..b200b61 --- /dev/null +++ b/tests/cases/cycle-detection/output/add-property-to-mutual-recursion.out @@ -0,0 +1,33 @@ +--- add-property-to-mutual-recursion.json ++++ patched +@@ + "Person": { + "description": "A person who works at a company.", + "properties": { ++ "age": { ++ "type": "integer" ++ }, + "employer": { + "$ref": "#/components/schemas/Company" + }, + + +Result for patch: +[ + Change { + message: "object properties changed", + old_path: [ + "#/components/schemas/Person", + "#/components/schemas/Company/properties/ceo/$ref", + "#/paths/~1companies/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/Person", + "#/components/schemas/Company/properties/ceo/$ref", + "#/paths/~1companies/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Unhandled, + details: UnknownDifference, + }, +] diff --git a/tests/cases/cycle-shared-prefix/output/change-item-value-type.out b/tests/cases/cycle-detection/output/change-item-value-type.out similarity index 100% rename from tests/cases/cycle-shared-prefix/output/change-item-value-type.out rename to tests/cases/cycle-detection/output/change-item-value-type.out diff --git a/tests/cases/cycle-detection/output/change-mutual-recursion-property.out b/tests/cases/cycle-detection/output/change-mutual-recursion-property.out new file mode 100644 index 0000000..6bd6c9d --- /dev/null +++ b/tests/cases/cycle-detection/output/change-mutual-recursion-property.out @@ -0,0 +1,32 @@ +--- change-mutual-recursion-property.json ++++ patched +@@ + "$ref": "#/components/schemas/Company" + }, + "name": { +- "type": "string" ++ "type": "integer" + } + }, + "required": [ + + +Result for patch: +[ + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/Person/properties/name", + "#/components/schemas/Company/properties/ceo/$ref", + "#/paths/~1companies/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/Person/properties/name", + "#/components/schemas/Company/properties/ceo/$ref", + "#/paths/~1companies/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/cycle-detection/output/change-three-cycle-property.out b/tests/cases/cycle-detection/output/change-three-cycle-property.out new file mode 100644 index 0000000..1dc673e --- /dev/null +++ b/tests/cases/cycle-detection/output/change-three-cycle-property.out @@ -0,0 +1,32 @@ +--- change-three-cycle-property.json ++++ patched +@@ + "description": "Second node of a three-way cycle.", + "properties": { + "label": { +- "type": "string" ++ "type": "integer" + }, + "next": { + "$ref": "#/components/schemas/NodeC" + + +Result for patch: +[ + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/NodeB/properties/label", + "#/components/schemas/NodeA/properties/next/$ref", + "#/paths/~1three-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/NodeB/properties/label", + "#/components/schemas/NodeA/properties/next/$ref", + "#/paths/~1three-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/cycle-detection/output/change-wrapped-cycle-property.out b/tests/cases/cycle-detection/output/change-wrapped-cycle-property.out new file mode 100644 index 0000000..4c9bafe --- /dev/null +++ b/tests/cases/cycle-detection/output/change-wrapped-cycle-property.out @@ -0,0 +1,32 @@ +--- change-wrapped-cycle-property.json ++++ patched +@@ + ] + }, + "value": { +- "type": "string" ++ "type": "integer" + } + }, + "type": "object" + + +Result for patch: +[ + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/Wrapped/properties/value", + "#/components/schemas/Wrapper/properties/child/0/$ref", + "#/paths/~1wrapped-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/Wrapped/properties/value", + "#/components/schemas/Wrapper/properties/child/0/$ref", + "#/paths/~1wrapped-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/cycle-detection/patch/add-property-to-mutual-recursion.json b/tests/cases/cycle-detection/patch/add-property-to-mutual-recursion.json new file mode 100644 index 0000000..ad62697 --- /dev/null +++ b/tests/cases/cycle-detection/patch/add-property-to-mutual-recursion.json @@ -0,0 +1,9 @@ +[ + { + "op": "add", + "path": "/components/schemas/Person/properties/age", + "value": { + "type": "integer" + } + } +] diff --git a/tests/cases/cycle-shared-prefix/patch/change-item-value-type.json b/tests/cases/cycle-detection/patch/change-item-value-type.json similarity index 100% rename from tests/cases/cycle-shared-prefix/patch/change-item-value-type.json rename to tests/cases/cycle-detection/patch/change-item-value-type.json diff --git a/tests/cases/cycle-detection/patch/change-mutual-recursion-property.json b/tests/cases/cycle-detection/patch/change-mutual-recursion-property.json new file mode 100644 index 0000000..a9c7613 --- /dev/null +++ b/tests/cases/cycle-detection/patch/change-mutual-recursion-property.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/components/schemas/Person/properties/name/type", + "value": "integer" + } +] diff --git a/tests/cases/cycle-detection/patch/change-three-cycle-property.json b/tests/cases/cycle-detection/patch/change-three-cycle-property.json new file mode 100644 index 0000000..0c481f7 --- /dev/null +++ b/tests/cases/cycle-detection/patch/change-three-cycle-property.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/components/schemas/NodeB/properties/label/type", + "value": "integer" + } +] diff --git a/tests/cases/cycle-detection/patch/change-wrapped-cycle-property.json b/tests/cases/cycle-detection/patch/change-wrapped-cycle-property.json new file mode 100644 index 0000000..8380ed5 --- /dev/null +++ b/tests/cases/cycle-detection/patch/change-wrapped-cycle-property.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/components/schemas/Wrapped/properties/value/type", + "value": "integer" + } +] diff --git a/tests/cases/cycle-shared-prefix/base.json b/tests/cases/cycle-shared-prefix/base.json deleted file mode 100644 index 7a409a3..0000000 --- a/tests/cases/cycle-shared-prefix/base.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Cycle shared-prefix detection test fixture", - "version": "1.0.0", - "description": "Item should not be detected as a prefix of ItemPage." - }, - "paths": { - "/items": { - "get": { - "operationId": "list_items", - "responses": { - "200": { - "description": "A page of items", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ItemPage" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "ItemPage": { - "description": "A page of items", - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Item" - } - } - }, - "required": ["items"] - }, - "Item": { - "description": "An item", - "type": "object", - "properties": { - "value": { - "type": "string" - } - }, - "required": ["value"] - } - } - } -} From e62d18e223083327c5666a80becd7e2b369ca34d Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 8 May 2026 15:39:39 -0700 Subject: [PATCH 2/3] even more cycle detection tests Created using spr 1.3.6-beta.1 --- tests/cases/cycle-detection/base.json | 70 ++++++++++++++++ ...asymmetric-unroll-with-property-change.out | 82 +++++++++++++++++++ .../output/swap-different-named-cycle.out | 53 ++++++++++++ ...symmetric-unroll-with-property-change.json | 27 ++++++ .../patch/swap-different-named-cycle.json | 14 ++++ 5 files changed, 246 insertions(+) create mode 100644 tests/cases/cycle-detection/output/asymmetric-unroll-with-property-change.out create mode 100644 tests/cases/cycle-detection/output/swap-different-named-cycle.out create mode 100644 tests/cases/cycle-detection/patch/asymmetric-unroll-with-property-change.json create mode 100644 tests/cases/cycle-detection/patch/swap-different-named-cycle.json diff --git a/tests/cases/cycle-detection/base.json b/tests/cases/cycle-detection/base.json index 131d54f..0e5255a 100644 --- a/tests/cases/cycle-detection/base.json +++ b/tests/cases/cycle-detection/base.json @@ -90,6 +90,40 @@ } } } + }, + "/direct-loop": { + "get": { + "operationId": "get_direct_loop", + "responses": { + "200": { + "description": "Direct self-cycle; patches may replace this with an unrolled cycle through an intermediate schema", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DirectLoop" + } + } + } + } + } + } + }, + "/self-cycle": { + "get": { + "operationId": "get_self_cycle", + "responses": { + "200": { + "description": "Self-cycle through SelfCycleA; patches may replace this with SelfCycleB to exercise pair-keyed cycle detection across different-named tops", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SelfCycleA" + } + } + } + } + } + } } }, "components": { @@ -210,6 +244,42 @@ ] } } + }, + "DirectLoop": { + "description": "Direct self-cycle for the asymmetric-unroll test.", + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "next": { + "$ref": "#/components/schemas/DirectLoop" + } + } + }, + "SelfCycleA": { + "description": "Self-recursive schema A; paired with SelfCycleB to test pair-keyed cycle detection across different-named tops.", + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "next": { + "$ref": "#/components/schemas/SelfCycleA" + } + } + }, + "SelfCycleB": { + "description": "Self-recursive schema B; structurally similar to SelfCycleA but distinct so a swap exercises (A, B) pair traversal.", + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "next": { + "$ref": "#/components/schemas/SelfCycleB" + } + } } } } diff --git a/tests/cases/cycle-detection/output/asymmetric-unroll-with-property-change.out b/tests/cases/cycle-detection/output/asymmetric-unroll-with-property-change.out new file mode 100644 index 0000000..11ebd94 --- /dev/null +++ b/tests/cases/cycle-detection/output/asymmetric-unroll-with-property-change.out @@ -0,0 +1,82 @@ +--- asymmetric-unroll-with-property-change.json ++++ patched +@@ + "description": "Direct self-cycle for the asymmetric-unroll test.", + "properties": { + "next": { +- "$ref": "#/components/schemas/DirectLoop" ++ "$ref": "#/components/schemas/LoopIndirect" + }, + "value": { +- "type": "string" ++ "type": "integer" + } + }, + "type": "object" +@@ + ], + "type": "object" + }, ++ "LoopIndirect": { ++ "description": "Intermediate schema introduced to unroll DirectLoop's self-cycle by one level.", ++ "properties": { ++ "next": { ++ "$ref": "#/components/schemas/DirectLoop" ++ } ++ }, ++ "type": "object" ++ }, + "NodeA": { + "description": "First node of a three-way cycle.", + "properties": { + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/DirectLoop", + "#/components/schemas/DirectLoop/properties/next/$ref", + "#/paths/~1direct-loop/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/LoopIndirect", + "#/components/schemas/DirectLoop/properties/next/$ref", + "#/paths/~1direct-loop/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, + Change { + message: "object properties changed", + old_path: [ + "#/components/schemas/DirectLoop", + "#/components/schemas/DirectLoop/properties/next/$ref", + "#/paths/~1direct-loop/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/LoopIndirect", + "#/components/schemas/DirectLoop/properties/next/$ref", + "#/paths/~1direct-loop/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Unhandled, + details: UnknownDifference, + }, + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/DirectLoop/properties/value", + "#/paths/~1direct-loop/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/DirectLoop/properties/value", + "#/paths/~1direct-loop/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/cycle-detection/output/swap-different-named-cycle.out b/tests/cases/cycle-detection/output/swap-different-named-cycle.out new file mode 100644 index 0000000..dd466b2 --- /dev/null +++ b/tests/cases/cycle-detection/output/swap-different-named-cycle.out @@ -0,0 +1,53 @@ +--- swap-different-named-cycle.json ++++ patched +@@ + "$ref": "#/components/schemas/SelfCycleB" + }, + "value": { +- "type": "string" ++ "type": "integer" + } + }, + "type": "object" +@@ + "content": { + "application/json": { + "schema": { +- "$ref": "#/components/schemas/SelfCycleA" ++ "$ref": "#/components/schemas/SelfCycleB" + } + } + }, + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/SelfCycleA", + "#/paths/~1self-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/SelfCycleB", + "#/paths/~1self-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/SelfCycleA/properties/value", + "#/paths/~1self-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/SelfCycleB/properties/value", + "#/paths/~1self-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/cycle-detection/patch/asymmetric-unroll-with-property-change.json b/tests/cases/cycle-detection/patch/asymmetric-unroll-with-property-change.json new file mode 100644 index 0000000..f6bbc3d --- /dev/null +++ b/tests/cases/cycle-detection/patch/asymmetric-unroll-with-property-change.json @@ -0,0 +1,27 @@ +[ + { + "op": "replace", + "path": "/components/schemas/DirectLoop/properties/value/type", + "value": "integer" + }, + { + "op": "replace", + "path": "/components/schemas/DirectLoop/properties/next", + "value": { + "$ref": "#/components/schemas/LoopIndirect" + } + }, + { + "op": "add", + "path": "/components/schemas/LoopIndirect", + "value": { + "description": "Intermediate schema introduced to unroll DirectLoop's self-cycle by one level.", + "type": "object", + "properties": { + "next": { + "$ref": "#/components/schemas/DirectLoop" + } + } + } + } +] diff --git a/tests/cases/cycle-detection/patch/swap-different-named-cycle.json b/tests/cases/cycle-detection/patch/swap-different-named-cycle.json new file mode 100644 index 0000000..5323a28 --- /dev/null +++ b/tests/cases/cycle-detection/patch/swap-different-named-cycle.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/paths/~1self-cycle/get/responses/200/content/application~1json/schema", + "value": { + "$ref": "#/components/schemas/SelfCycleB" + } + }, + { + "op": "replace", + "path": "/components/schemas/SelfCycleB/properties/value/type", + "value": "integer" + } +] From 9bb3b44e6110a6ab34b1b082a2a213b3f1dcff14 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 8 May 2026 16:24:40 -0700 Subject: [PATCH 3/3] add a test for A->B->A to B->A->B Created using spr 1.3.6-beta.1 --- src/schema.rs | 4 -- tests/cases/cycle-detection/base.json | 35 ++++++++++++++ .../output/swap-alternating-cycle-entry.out | 46 +++++++++++++++++++ .../patch/swap-alternating-cycle-entry.json | 7 +++ 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 tests/cases/cycle-detection/output/swap-alternating-cycle-entry.out create mode 100644 tests/cases/cycle-detection/patch/swap-alternating-cycle-entry.json diff --git a/src/schema.rs b/src/schema.rs index e6d68c8..699ffca 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -189,10 +189,6 @@ impl Compare { old_schema: Contextual<'_, &Schema>, new_schema: Contextual<'_, &Schema>, ) -> anyhow::Result { - // We wait for both new and old to contain a cycle; this ensures that - // we consider "unrolled" cycles properly. There is a possibility of - // getting stuck in an A->B->A / B->A->B cycle... we can address that - // should that construction arise. if old_schema.context().stack().contains_cycle() && new_schema.context().stack().contains_cycle() { diff --git a/tests/cases/cycle-detection/base.json b/tests/cases/cycle-detection/base.json index 0e5255a..1670d76 100644 --- a/tests/cases/cycle-detection/base.json +++ b/tests/cases/cycle-detection/base.json @@ -124,6 +124,23 @@ } } } + }, + "/alternating-cycle": { + "get": { + "operationId": "get_alternating_cycle", + "responses": { + "200": { + "description": "Mutual cycle AltX <-> AltY. Swapping the entry from AltX to AltY makes old-side traversal walk AltX -> AltY -> AltX -> ... while new-side walks AltY -> AltX -> AltY -> ..., exercising the alternating (A,B)/(B,A) pair-keyed traversal.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AltX" + } + } + } + } + } + } } }, "components": { @@ -280,6 +297,24 @@ "$ref": "#/components/schemas/SelfCycleB" } } + }, + "AltX": { + "description": "Mutual cycle pair, side X. Structurally identical to AltY (object whose `next` refers to the partner); distinguishable only by name and this description.", + "type": "object", + "properties": { + "next": { + "$ref": "#/components/schemas/AltY" + } + } + }, + "AltY": { + "description": "Mutual cycle pair, side Y. Structurally identical to AltX (object whose `next` refers to the partner); distinguishable only by name and this description.", + "type": "object", + "properties": { + "next": { + "$ref": "#/components/schemas/AltX" + } + } } } } diff --git a/tests/cases/cycle-detection/output/swap-alternating-cycle-entry.out b/tests/cases/cycle-detection/output/swap-alternating-cycle-entry.out new file mode 100644 index 0000000..118982b --- /dev/null +++ b/tests/cases/cycle-detection/output/swap-alternating-cycle-entry.out @@ -0,0 +1,46 @@ +--- swap-alternating-cycle-entry.json ++++ patched +@@ + "content": { + "application/json": { + "schema": { +- "$ref": "#/components/schemas/AltX" ++ "$ref": "#/components/schemas/AltY" + } + } + }, + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/AltX", + "#/paths/~1alternating-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/AltY", + "#/paths/~1alternating-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/AltY", + "#/components/schemas/AltX/properties/next/$ref", + "#/paths/~1alternating-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/AltX", + "#/components/schemas/AltY/properties/next/$ref", + "#/paths/~1alternating-cycle/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/cycle-detection/patch/swap-alternating-cycle-entry.json b/tests/cases/cycle-detection/patch/swap-alternating-cycle-entry.json new file mode 100644 index 0000000..6842d48 --- /dev/null +++ b/tests/cases/cycle-detection/patch/swap-alternating-cycle-entry.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/paths/~1alternating-cycle/get/responses/200/content/application~1json/schema/$ref", + "value": "#/components/schemas/AltY" + } +]