From 5efc9e41f100e486dfd292dc80f243d1b4c72b0d Mon Sep 17 00:00:00 2001 From: Rain Date: Mon, 8 Dec 2025 18:59:54 +0000 Subject: [PATCH 1/3] [spr] initial version Created using spr 1.3.6-beta.1 --- src/context.rs | 2 +- src/schema.rs | 238 ++++++++++++++++-- tests/cases/simple/base.json | 35 +++ tests/cases/simple/output/add-operation.out | 2 +- .../simple/output/add-type-extension.out | 4 +- tests/cases/simple/output/allof-to-anyof.out | 36 +++ .../allof-to-oneof-with-type-change.out | 62 +++++ tests/cases/simple/output/allof-to-oneof.out | 37 +++ .../output/allof-to-ref-with-type-change.out | 61 +++++ tests/cases/simple/output/allof-to-ref.out | 35 +++ tests/cases/simple/output/anyof-to-allof.out | 36 +++ tests/cases/simple/output/anyof-to-oneof.out | 37 +++ tests/cases/simple/output/anyof-to-ref.out | 35 +++ ...change-operation-parameter-requirement.out | 2 +- .../change-operation-parameter-type.out | 2 +- .../simple/output/change-property-type.out | 6 +- .../cases/simple/output/modify-cycle-type.out | 2 +- tests/cases/simple/output/oneof-to-allof.out | 37 +++ tests/cases/simple/output/oneof-to-anyof.out | 37 +++ tests/cases/simple/output/oneof-to-ref.out | 35 +++ tests/cases/simple/output/ref-to-allof.out | 35 +++ tests/cases/simple/output/ref-to-anyof.out | 35 +++ tests/cases/simple/output/ref-to-oneof.out | 35 +++ .../output/remove-operation-parameter.out | 2 +- .../cases/simple/output/remove-operation.out | 2 +- .../cases/simple/output/type-indirection.out | 12 +- tests/cases/simple/output/type-rename.out | 2 +- .../simple/output/unhandled-add-prop.out | 10 +- .../wrapper-unchanged-with-type-change.out | 32 +++ tests/cases/simple/patch/allof-to-anyof.json | 14 ++ .../allof-to-oneof-with-type-change.json | 19 ++ tests/cases/simple/patch/allof-to-oneof.json | 14 ++ .../patch/allof-to-ref-with-type-change.json | 16 ++ tests/cases/simple/patch/allof-to-ref.json | 9 + tests/cases/simple/patch/anyof-to-allof.json | 14 ++ tests/cases/simple/patch/anyof-to-oneof.json | 14 ++ tests/cases/simple/patch/anyof-to-ref.json | 9 + tests/cases/simple/patch/oneof-to-allof.json | 14 ++ tests/cases/simple/patch/oneof-to-anyof.json | 14 ++ tests/cases/simple/patch/oneof-to-ref.json | 9 + tests/cases/simple/patch/ref-to-allof.json | 14 ++ tests/cases/simple/patch/ref-to-anyof.json | 14 ++ tests/cases/simple/patch/ref-to-oneof.json | 14 ++ .../wrapper-unchanged-with-type-change.json | 7 + 44 files changed, 1065 insertions(+), 36 deletions(-) create mode 100644 tests/cases/simple/output/allof-to-anyof.out create mode 100644 tests/cases/simple/output/allof-to-oneof-with-type-change.out create mode 100644 tests/cases/simple/output/allof-to-oneof.out create mode 100644 tests/cases/simple/output/allof-to-ref-with-type-change.out create mode 100644 tests/cases/simple/output/allof-to-ref.out create mode 100644 tests/cases/simple/output/anyof-to-allof.out create mode 100644 tests/cases/simple/output/anyof-to-oneof.out create mode 100644 tests/cases/simple/output/anyof-to-ref.out create mode 100644 tests/cases/simple/output/oneof-to-allof.out create mode 100644 tests/cases/simple/output/oneof-to-anyof.out create mode 100644 tests/cases/simple/output/oneof-to-ref.out create mode 100644 tests/cases/simple/output/ref-to-allof.out create mode 100644 tests/cases/simple/output/ref-to-anyof.out create mode 100644 tests/cases/simple/output/ref-to-oneof.out create mode 100644 tests/cases/simple/output/wrapper-unchanged-with-type-change.out create mode 100644 tests/cases/simple/patch/allof-to-anyof.json create mode 100644 tests/cases/simple/patch/allof-to-oneof-with-type-change.json create mode 100644 tests/cases/simple/patch/allof-to-oneof.json create mode 100644 tests/cases/simple/patch/allof-to-ref-with-type-change.json create mode 100644 tests/cases/simple/patch/allof-to-ref.json create mode 100644 tests/cases/simple/patch/anyof-to-allof.json create mode 100644 tests/cases/simple/patch/anyof-to-oneof.json create mode 100644 tests/cases/simple/patch/anyof-to-ref.json create mode 100644 tests/cases/simple/patch/oneof-to-allof.json create mode 100644 tests/cases/simple/patch/oneof-to-anyof.json create mode 100644 tests/cases/simple/patch/oneof-to-ref.json create mode 100644 tests/cases/simple/patch/ref-to-allof.json create mode 100644 tests/cases/simple/patch/ref-to-anyof.json create mode 100644 tests/cases/simple/patch/ref-to-oneof.json create mode 100644 tests/cases/simple/patch/wrapper-unchanged-with-type-change.json diff --git a/src/context.rs b/src/context.rs index 71e0679..04c6c7e 100644 --- a/src/context.rs +++ b/src/context.rs @@ -47,7 +47,7 @@ impl<'a> Context<'a> { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Contextual<'a, T> { context: Context<'a>, value: T, diff --git a/src/schema.rs b/src/schema.rs index b24e5c0..5bfdef2 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,5 +1,7 @@ // Copyright 2025 Oxide Computer Company +use std::fmt; + use openapiv3::{AdditionalProperties, ArrayType, ObjectType, ReferenceOr, Schema, SchemaData}; use crate::{ @@ -42,13 +44,128 @@ impl Compare { old_schema: Contextual<'_, &ReferenceOr>, new_schema: Contextual<'_, &ReferenceOr>, ) -> anyhow::Result { - let (old_schema, old_context) = old_schema.contextual_resolve()?; - let (new_schema, new_context) = new_schema.contextual_resolve()?; + // Handle single-element wrapper equivalence (allOf/anyOf/oneOf with one + // item). These are semantically equivalent to their inner type. + if let Some(result) = + self.try_compare_unwrapped(dry_run, comparison, &old_schema, &new_schema)? + { + Ok(result) + } else { + // Normal path: resolve and compare. + let (old_schema, old_context) = old_schema.contextual_resolve()?; + let (new_schema, new_context) = new_schema.contextual_resolve()?; + + let old_schema = Contextual::new(old_context, old_schema.as_ref()); + let new_schema = Contextual::new(new_context, new_schema.as_ref()); + + self.compare_schema(comparison, dry_run, old_schema, new_schema) + } + } + + /// Try to compare schemas by unwrapping single-element wrappers. + /// + /// Returns `Some(result)` if unwrapping was applicable, `None` to fall + /// through. + fn try_compare_unwrapped( + &mut self, + dry_run: bool, + comparison: SchemaComparison, + old_schema: &Contextual<'_, &ReferenceOr>, + new_schema: &Contextual<'_, &ReferenceOr>, + ) -> anyhow::Result> { + use SchemaRefKind::*; - let old_schema = Contextual::new(old_context, old_schema.as_ref()); - let new_schema = Contextual::new(new_context, new_schema.as_ref()); + let old_kind = classify_schema_ref(old_schema.as_ref()); + let new_kind = classify_schema_ref(new_schema.as_ref()); - self.compare_schema(comparison, dry_run, old_schema, new_schema) + match (old_kind, new_kind) { + // Both are single-element wrappers. Compare metadata and recurse + ( + SingleElement { + inner: old_inner, + metadata: old_meta, + }, + SingleElement { + inner: new_inner, + metadata: new_meta, + }, + ) => { + if old_meta != new_meta { + self.push_change( + "schema metadata changed", + old_schema, + new_schema, + comparison.into(), + ChangeClass::Trivial, + ChangeDetails::Metadata, + ); + } + let old_inner = old_schema.append_deref(old_inner, "0"); + let new_inner = new_schema.append_deref(new_inner, "0"); + Ok(Some(self.compare_schema_ref_helper( + dry_run, comparison, old_inner, new_inner, + )?)) + } + // Old is single-element wrapper, new is bare ref. + ( + SingleElement { + inner: old_inner, + metadata: old_meta, + }, + BareRef, + ) => { + if has_meaningful_metadata(old_meta) { + self.push_change( + "schema metadata changed", + old_schema, + new_schema, + comparison.into(), + ChangeClass::Trivial, + ChangeDetails::Metadata, + ); + } + let old_inner = old_schema.append_deref(old_inner, "0"); + Ok(Some(self.compare_schema_ref_helper( + dry_run, + comparison, + old_inner, + new_schema.clone(), + )?)) + } + // Old is bare ref, new is single-element wrapper. + ( + BareRef, + SingleElement { + inner: new_inner, + metadata: new_meta, + }, + ) => { + if has_meaningful_metadata(new_meta) { + self.push_change( + "schema metadata changed", + old_schema, + new_schema, + comparison.into(), + ChangeClass::Trivial, + ChangeDetails::Metadata, + ); + } + let new_inner = new_schema.append_deref(new_inner, "0"); + Ok(Some(self.compare_schema_ref_helper( + dry_run, + comparison, + old_schema.clone(), + new_inner, + )?)) + } + // No unwrapping applicable - fall through to normal comparison. + (BareRef, BareRef) + | (BareRef, Other) + | (Other, BareRef) + | (Other, Other) + | (SingleElement { .. }, Other) + | (Other, SingleElement { .. }) => Ok(None), + } } fn compare_schema( @@ -244,15 +361,19 @@ impl Compare { ) } } - _ => self.schema_push_change( - dry_run, - "schema kinds changed".to_string(), - &old_schema_kind, - &new_schema_kind, - comparison, - ChangeClass::Incompatible, - ChangeDetails::Datatype, - ), + _ => { + let old_tag = SchemaKindTag::new(&old_schema_kind); + let new_tag = SchemaKindTag::new(&new_schema_kind); + self.schema_push_change( + dry_run, + format!("schema kind changed from {} to {}", old_tag, new_tag), + &old_schema_kind, + &new_schema_kind, + comparison, + ChangeClass::Incompatible, + ChangeDetails::Datatype, + ) + } } } @@ -668,3 +789,92 @@ impl Compare { Ok(false) } } + +#[derive(Clone, Debug)] +enum SchemaKindTag { + Type, + OneOf, + AllOf, + AnyOf, + Not, + Any, +} + +impl fmt::Display for SchemaKindTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Type => write!(f, "regular type"), + Self::OneOf => write!(f, "oneOf"), + Self::AllOf => write!(f, "allOf"), + Self::AnyOf => write!(f, "anyOf"), + Self::Not => write!(f, "not"), + Self::Any => write!(f, "any"), + } + } +} + +impl SchemaKindTag { + fn new(kind: &openapiv3::SchemaKind) -> Self { + match kind { + openapiv3::SchemaKind::Type(_) => Self::Type, + openapiv3::SchemaKind::OneOf { .. } => Self::OneOf, + openapiv3::SchemaKind::AllOf { .. } => Self::AllOf, + openapiv3::SchemaKind::AnyOf { .. } => Self::AnyOf, + openapiv3::SchemaKind::Not { .. } => Self::Not, + openapiv3::SchemaKind::Any { .. } => Self::Any, + } + } +} + +/// Classification of a schema reference for wrapper unwrapping purposes. +enum SchemaRefKind<'a> { + /// A bare $ref - can be compared with single-element wrappers. + BareRef, + /// A single-element allOf/anyOf/oneOf wrapper, semantically equivalent to + /// the inner type. + SingleElement { + inner: &'a ReferenceOr, + metadata: &'a SchemaData, + }, + /// Something else (multi-element wrapper or non-wrapper type). + Other, +} + +/// Classify a schema reference for wrapper unwrapping purposes. +fn classify_schema_ref(schema_ref: &ReferenceOr) -> SchemaRefKind<'_> { + match schema_ref { + ReferenceOr::Reference { .. } => SchemaRefKind::BareRef, + ReferenceOr::Item(schema) => match &schema.schema_kind { + openapiv3::SchemaKind::AllOf { all_of } if all_of.len() == 1 => { + SchemaRefKind::SingleElement { + inner: all_of.first().unwrap(), + metadata: &schema.schema_data, + } + } + openapiv3::SchemaKind::AnyOf { any_of } if any_of.len() == 1 => { + SchemaRefKind::SingleElement { + inner: any_of.first().unwrap(), + metadata: &schema.schema_data, + } + } + openapiv3::SchemaKind::OneOf { one_of } if one_of.len() == 1 => { + SchemaRefKind::SingleElement { + inner: one_of.first().unwrap(), + metadata: &schema.schema_data, + } + } + openapiv3::SchemaKind::AllOf { .. } + | openapiv3::SchemaKind::AnyOf { .. } + | openapiv3::SchemaKind::OneOf { .. } + | openapiv3::SchemaKind::Type(_) + | openapiv3::SchemaKind::Not { .. } + | openapiv3::SchemaKind::Any(_) => SchemaRefKind::Other, + }, + } +} + +/// Check if schema_data has any non-default values that would constitute +/// metadata worth preserving. +fn has_meaningful_metadata(data: &SchemaData) -> bool { + *data != SchemaData::default() +} diff --git a/tests/cases/simple/base.json b/tests/cases/simple/base.json index 1cd9aa0..0031433 100644 --- a/tests/cases/simple/base.json +++ b/tests/cases/simple/base.json @@ -80,12 +80,47 @@ "message": { "type": "string", "description": "The greeting message" + }, + "via_ref": { + "$ref": "#/components/schemas/SubType" + }, + "via_allof": { + "description": "Via allOf.", + "allOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + }, + "via_anyof": { + "description": "Via anyOf.", + "anyOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + }, + "via_oneof": { + "description": "Via oneOf.", + "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] } }, "required": [ "message" ] }, + "SubType": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, "Tree": { "type": "object", "properties": { diff --git a/tests/cases/simple/output/add-operation.out b/tests/cases/simple/output/add-operation.out index 0dd6a8a..6b20975 100644 --- a/tests/cases/simple/output/add-operation.out +++ b/tests/cases/simple/output/add-operation.out @@ -1,6 +1,6 @@ --- add-operation.json +++ patched -@@ -33,6 +33,16 @@ +@@ -68,6 +68,16 @@ }, "openapi": "3.0.0", "paths": { diff --git a/tests/cases/simple/output/add-type-extension.out b/tests/cases/simple/output/add-type-extension.out index 8d01c23..01ae654 100644 --- a/tests/cases/simple/output/add-type-extension.out +++ b/tests/cases/simple/output/add-type-extension.out @@ -1,6 +1,6 @@ --- add-type-extension.json +++ patched -@@ -11,7 +11,10 @@ +@@ -38,7 +38,10 @@ "required": [ "message" ], @@ -10,7 +10,7 @@ + "mumble": "frotz" + } }, - "Tree": { + "SubType": { "properties": { diff --git a/tests/cases/simple/output/allof-to-anyof.out b/tests/cases/simple/output/allof-to-anyof.out new file mode 100644 index 0000000..c3b8683 --- /dev/null +++ b/tests/cases/simple/output/allof-to-anyof.out @@ -0,0 +1,36 @@ +--- allof-to-anyof.json ++++ patched +@@ -8,12 +8,12 @@ + "type": "string" + }, + "via_allof": { +- "allOf": [ ++ "anyOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ], +- "description": "Via allOf." ++ "description": "Via anyOf." + }, + "via_anyof": { + "anyOf": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/allof-to-oneof-with-type-change.out b/tests/cases/simple/output/allof-to-oneof-with-type-change.out new file mode 100644 index 0000000..f39c8e9 --- /dev/null +++ b/tests/cases/simple/output/allof-to-oneof-with-type-change.out @@ -0,0 +1,62 @@ +--- allof-to-oneof-with-type-change.json ++++ patched +@@ -8,12 +8,12 @@ + "type": "string" + }, + "via_allof": { +- "allOf": [ ++ "description": "Via oneOf.", ++ "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } +- ], +- "description": "Via allOf." ++ ] + }, + "via_anyof": { + "anyOf": [ +@@ -43,7 +43,7 @@ + "SubType": { + "properties": { + "value": { +- "type": "string" ++ "type": "integer" + } + }, + "type": "object" + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/SubType/properties/value", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/SubType/properties/value", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/simple/output/allof-to-oneof.out b/tests/cases/simple/output/allof-to-oneof.out new file mode 100644 index 0000000..55d9a71 --- /dev/null +++ b/tests/cases/simple/output/allof-to-oneof.out @@ -0,0 +1,37 @@ +--- allof-to-oneof.json ++++ patched +@@ -8,12 +8,12 @@ + "type": "string" + }, + "via_allof": { +- "allOf": [ ++ "description": "Via oneOf.", ++ "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } +- ], +- "description": "Via allOf." ++ ] + }, + "via_anyof": { + "anyOf": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/allof-to-ref-with-type-change.out b/tests/cases/simple/output/allof-to-ref-with-type-change.out new file mode 100644 index 0000000..377b449 --- /dev/null +++ b/tests/cases/simple/output/allof-to-ref-with-type-change.out @@ -0,0 +1,61 @@ +--- allof-to-ref-with-type-change.json ++++ patched +@@ -8,12 +8,7 @@ + "type": "string" + }, + "via_allof": { +- "allOf": [ +- { +- "$ref": "#/components/schemas/SubType" +- } +- ], +- "description": "Via allOf." ++ "$ref": "#/components/schemas/SubType" + }, + "via_anyof": { + "anyOf": [ +@@ -42,6 +37,9 @@ + }, + "SubType": { + "properties": { ++ "extra": { ++ "type": "integer" ++ }, + "value": { + "type": "string" + } + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, + Change { + message: "object properties changed", + old_path: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_allof/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Unhandled, + details: UnknownDifference, + }, +] diff --git a/tests/cases/simple/output/allof-to-ref.out b/tests/cases/simple/output/allof-to-ref.out new file mode 100644 index 0000000..ad04101 --- /dev/null +++ b/tests/cases/simple/output/allof-to-ref.out @@ -0,0 +1,35 @@ +--- allof-to-ref.json ++++ patched +@@ -8,12 +8,7 @@ + "type": "string" + }, + "via_allof": { +- "allOf": [ +- { +- "$ref": "#/components/schemas/SubType" +- } +- ], +- "description": "Via allOf." ++ "$ref": "#/components/schemas/SubType" + }, + "via_anyof": { + "anyOf": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/anyof-to-allof.out b/tests/cases/simple/output/anyof-to-allof.out new file mode 100644 index 0000000..66580d6 --- /dev/null +++ b/tests/cases/simple/output/anyof-to-allof.out @@ -0,0 +1,36 @@ +--- anyof-to-allof.json ++++ patched +@@ -16,12 +16,12 @@ + "description": "Via allOf." + }, + "via_anyof": { +- "anyOf": [ ++ "allOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ], +- "description": "Via anyOf." ++ "description": "Via allOf." + }, + "via_oneof": { + "description": "Via oneOf.", + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/anyof-to-oneof.out b/tests/cases/simple/output/anyof-to-oneof.out new file mode 100644 index 0000000..baade56 --- /dev/null +++ b/tests/cases/simple/output/anyof-to-oneof.out @@ -0,0 +1,37 @@ +--- anyof-to-oneof.json ++++ patched +@@ -16,12 +16,12 @@ + "description": "Via allOf." + }, + "via_anyof": { +- "anyOf": [ ++ "description": "Via oneOf.", ++ "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } +- ], +- "description": "Via anyOf." ++ ] + }, + "via_oneof": { + "description": "Via oneOf.", + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/anyof-to-ref.out b/tests/cases/simple/output/anyof-to-ref.out new file mode 100644 index 0000000..c2c8fad --- /dev/null +++ b/tests/cases/simple/output/anyof-to-ref.out @@ -0,0 +1,35 @@ +--- anyof-to-ref.json ++++ patched +@@ -16,12 +16,7 @@ + "description": "Via allOf." + }, + "via_anyof": { +- "anyOf": [ +- { +- "$ref": "#/components/schemas/SubType" +- } +- ], +- "description": "Via anyOf." ++ "$ref": "#/components/schemas/SubType" + }, + "via_oneof": { + "description": "Via oneOf.", + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/change-operation-parameter-requirement.out b/tests/cases/simple/output/change-operation-parameter-requirement.out index d8437ac..b00c292 100644 --- a/tests/cases/simple/output/change-operation-parameter-requirement.out +++ b/tests/cases/simple/output/change-operation-parameter-requirement.out @@ -1,6 +1,6 @@ --- change-operation-parameter-requirement.json +++ patched -@@ -48,7 +48,7 @@ +@@ -83,7 +83,7 @@ "description": "Language for the greeting", "in": "query", "name": "language", diff --git a/tests/cases/simple/output/change-operation-parameter-type.out b/tests/cases/simple/output/change-operation-parameter-type.out index 2c918f1..0ca2ac7 100644 --- a/tests/cases/simple/output/change-operation-parameter-type.out +++ b/tests/cases/simple/output/change-operation-parameter-type.out @@ -1,6 +1,6 @@ --- change-operation-parameter-type.json +++ patched -@@ -50,7 +50,7 @@ +@@ -85,7 +85,7 @@ "name": "language", "required": false, "schema": { diff --git a/tests/cases/simple/output/change-property-type.out b/tests/cases/simple/output/change-property-type.out index 7ea9e2a..136564f 100644 --- a/tests/cases/simple/output/change-property-type.out +++ b/tests/cases/simple/output/change-property-type.out @@ -6,9 +6,9 @@ "description": "The greeting message", - "type": "string" + "type": "integer" - } - }, - "required": [ + }, + "via_allof": { + "allOf": [ Result for patch: diff --git a/tests/cases/simple/output/modify-cycle-type.out b/tests/cases/simple/output/modify-cycle-type.out index e9b0596..3414d50 100644 --- a/tests/cases/simple/output/modify-cycle-type.out +++ b/tests/cases/simple/output/modify-cycle-type.out @@ -1,6 +1,6 @@ --- modify-cycle-type.json +++ patched -@@ -16,10 +16,7 @@ +@@ -51,10 +51,7 @@ "Tree": { "properties": { "children": { diff --git a/tests/cases/simple/output/oneof-to-allof.out b/tests/cases/simple/output/oneof-to-allof.out new file mode 100644 index 0000000..ed58fb9 --- /dev/null +++ b/tests/cases/simple/output/oneof-to-allof.out @@ -0,0 +1,37 @@ +--- oneof-to-allof.json ++++ patched +@@ -24,12 +24,12 @@ + "description": "Via anyOf." + }, + "via_oneof": { +- "description": "Via oneOf.", +- "oneOf": [ ++ "allOf": [ + { + "$ref": "#/components/schemas/SubType" + } +- ] ++ ], ++ "description": "Via allOf." + }, + "via_ref": { + "$ref": "#/components/schemas/SubType" + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/oneof-to-anyof.out b/tests/cases/simple/output/oneof-to-anyof.out new file mode 100644 index 0000000..aa360f6 --- /dev/null +++ b/tests/cases/simple/output/oneof-to-anyof.out @@ -0,0 +1,37 @@ +--- oneof-to-anyof.json ++++ patched +@@ -24,12 +24,12 @@ + "description": "Via anyOf." + }, + "via_oneof": { +- "description": "Via oneOf.", +- "oneOf": [ ++ "anyOf": [ + { + "$ref": "#/components/schemas/SubType" + } +- ] ++ ], ++ "description": "Via anyOf." + }, + "via_ref": { + "$ref": "#/components/schemas/SubType" + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/oneof-to-ref.out b/tests/cases/simple/output/oneof-to-ref.out new file mode 100644 index 0000000..ca77305 --- /dev/null +++ b/tests/cases/simple/output/oneof-to-ref.out @@ -0,0 +1,35 @@ +--- oneof-to-ref.json ++++ patched +@@ -24,12 +24,7 @@ + "description": "Via anyOf." + }, + "via_oneof": { +- "description": "Via oneOf.", +- "oneOf": [ +- { +- "$ref": "#/components/schemas/SubType" +- } +- ] ++ "$ref": "#/components/schemas/SubType" + }, + "via_ref": { + "$ref": "#/components/schemas/SubType" + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/ref-to-allof.out b/tests/cases/simple/output/ref-to-allof.out new file mode 100644 index 0000000..63a17f0 --- /dev/null +++ b/tests/cases/simple/output/ref-to-allof.out @@ -0,0 +1,35 @@ +--- ref-to-allof.json ++++ patched +@@ -32,7 +32,12 @@ + ] + }, + "via_ref": { +- "$ref": "#/components/schemas/SubType" ++ "allOf": [ ++ { ++ "$ref": "#/components/schemas/SubType" ++ } ++ ], ++ "description": "Via allOf." + } + }, + "required": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/ref-to-anyof.out b/tests/cases/simple/output/ref-to-anyof.out new file mode 100644 index 0000000..0559391 --- /dev/null +++ b/tests/cases/simple/output/ref-to-anyof.out @@ -0,0 +1,35 @@ +--- ref-to-anyof.json ++++ patched +@@ -32,7 +32,12 @@ + ] + }, + "via_ref": { +- "$ref": "#/components/schemas/SubType" ++ "anyOf": [ ++ { ++ "$ref": "#/components/schemas/SubType" ++ } ++ ], ++ "description": "Via anyOf." + } + }, + "required": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/ref-to-oneof.out b/tests/cases/simple/output/ref-to-oneof.out new file mode 100644 index 0000000..9219537 --- /dev/null +++ b/tests/cases/simple/output/ref-to-oneof.out @@ -0,0 +1,35 @@ +--- ref-to-oneof.json ++++ patched +@@ -32,7 +32,12 @@ + ] + }, + "via_ref": { +- "$ref": "#/components/schemas/SubType" ++ "description": "Via oneOf.", ++ "oneOf": [ ++ { ++ "$ref": "#/components/schemas/SubType" ++ } ++ ] + } + }, + "required": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/remove-operation-parameter.out b/tests/cases/simple/output/remove-operation-parameter.out index f7b90e2..7001216 100644 --- a/tests/cases/simple/output/remove-operation-parameter.out +++ b/tests/cases/simple/output/remove-operation-parameter.out @@ -1,6 +1,6 @@ --- remove-operation-parameter.json +++ patched -@@ -43,15 +43,6 @@ +@@ -78,15 +78,6 @@ "schema": { "type": "string" } diff --git a/tests/cases/simple/output/remove-operation.out b/tests/cases/simple/output/remove-operation.out index 06d2ddb..57c225b 100644 --- a/tests/cases/simple/output/remove-operation.out +++ b/tests/cases/simple/output/remove-operation.out @@ -1,6 +1,6 @@ --- remove-operation.json +++ patched -@@ -69,18 +69,7 @@ +@@ -104,18 +104,7 @@ "summary": "Say hello" } }, diff --git a/tests/cases/simple/output/type-indirection.out b/tests/cases/simple/output/type-indirection.out index be61afb..44cfe9b 100644 --- a/tests/cases/simple/output/type-indirection.out +++ b/tests/cases/simple/output/type-indirection.out @@ -7,10 +7,10 @@ - "description": "The greeting message", - "type": "string" + "$ref": "#/components/schemas/GreetingResponseMessage" - } - }, - "required": [ -@@ -13,6 +12,13 @@ + }, + "via_allof": { + "allOf": [ +@@ -40,6 +39,13 @@ ], "type": "object" }, @@ -21,9 +21,9 @@ + "jank": true + } + }, - "Tree": { + "SubType": { "properties": { - "children": { + "value": { Result for patch: diff --git a/tests/cases/simple/output/type-rename.out b/tests/cases/simple/output/type-rename.out index bfbf873..e1226df 100644 --- a/tests/cases/simple/output/type-rename.out +++ b/tests/cases/simple/output/type-rename.out @@ -9,7 +9,7 @@ "properties": { "message": { "description": "The greeting message", -@@ -59,7 +59,7 @@ +@@ -94,7 +94,7 @@ "content": { "application/json": { "schema": { diff --git a/tests/cases/simple/output/unhandled-add-prop.out b/tests/cases/simple/output/unhandled-add-prop.out index 93a407f..361e725 100644 --- a/tests/cases/simple/output/unhandled-add-prop.out +++ b/tests/cases/simple/output/unhandled-add-prop.out @@ -1,13 +1,17 @@ --- unhandled-add-prop.json +++ patched -@@ -6,10 +6,15 @@ - "message": { +@@ -7,6 +7,10 @@ "description": "The greeting message", "type": "string" -+ }, + }, + "source": { + "description": "Where the greeting originated", + "type": "string" ++ }, + "via_allof": { + "allOf": [ + { +@@ -36,7 +40,8 @@ } }, "required": [ diff --git a/tests/cases/simple/output/wrapper-unchanged-with-type-change.out b/tests/cases/simple/output/wrapper-unchanged-with-type-change.out new file mode 100644 index 0000000..6d0b478 --- /dev/null +++ b/tests/cases/simple/output/wrapper-unchanged-with-type-change.out @@ -0,0 +1,32 @@ +--- wrapper-unchanged-with-type-change.json ++++ patched +@@ -43,7 +43,7 @@ + "SubType": { + "properties": { + "value": { +- "type": "string" ++ "type": "integer" + } + }, + "type": "object" + + +Result for patch: +[ + Change { + message: "schema types changed", + old_path: [ + "#/components/schemas/SubType/properties/value", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/SubType/properties/value", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Incompatible, + details: UnknownDifference, + }, +] diff --git a/tests/cases/simple/patch/allof-to-anyof.json b/tests/cases/simple/patch/allof-to-anyof.json new file mode 100644 index 0000000..f7e8bea --- /dev/null +++ b/tests/cases/simple/patch/allof-to-anyof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_allof", + "value": { + "description": "Via anyOf.", + "anyOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/allof-to-oneof-with-type-change.json b/tests/cases/simple/patch/allof-to-oneof-with-type-change.json new file mode 100644 index 0000000..1b973c6 --- /dev/null +++ b/tests/cases/simple/patch/allof-to-oneof-with-type-change.json @@ -0,0 +1,19 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_allof", + "value": { + "description": "Via oneOf.", + "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + }, + { + "op": "replace", + "path": "/components/schemas/SubType/properties/value/type", + "value": "integer" + } +] diff --git a/tests/cases/simple/patch/allof-to-oneof.json b/tests/cases/simple/patch/allof-to-oneof.json new file mode 100644 index 0000000..6ba1e82 --- /dev/null +++ b/tests/cases/simple/patch/allof-to-oneof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_allof", + "value": { + "description": "Via oneOf.", + "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/allof-to-ref-with-type-change.json b/tests/cases/simple/patch/allof-to-ref-with-type-change.json new file mode 100644 index 0000000..f5d0da9 --- /dev/null +++ b/tests/cases/simple/patch/allof-to-ref-with-type-change.json @@ -0,0 +1,16 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_allof", + "value": { + "$ref": "#/components/schemas/SubType" + } + }, + { + "op": "add", + "path": "/components/schemas/SubType/properties/extra", + "value": { + "type": "integer" + } + } +] diff --git a/tests/cases/simple/patch/allof-to-ref.json b/tests/cases/simple/patch/allof-to-ref.json new file mode 100644 index 0000000..a5a5da7 --- /dev/null +++ b/tests/cases/simple/patch/allof-to-ref.json @@ -0,0 +1,9 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_allof", + "value": { + "$ref": "#/components/schemas/SubType" + } + } +] diff --git a/tests/cases/simple/patch/anyof-to-allof.json b/tests/cases/simple/patch/anyof-to-allof.json new file mode 100644 index 0000000..94a4eb6 --- /dev/null +++ b/tests/cases/simple/patch/anyof-to-allof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_anyof", + "value": { + "description": "Via allOf.", + "allOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/anyof-to-oneof.json b/tests/cases/simple/patch/anyof-to-oneof.json new file mode 100644 index 0000000..40ed32f --- /dev/null +++ b/tests/cases/simple/patch/anyof-to-oneof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_anyof", + "value": { + "description": "Via oneOf.", + "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/anyof-to-ref.json b/tests/cases/simple/patch/anyof-to-ref.json new file mode 100644 index 0000000..20c364b --- /dev/null +++ b/tests/cases/simple/patch/anyof-to-ref.json @@ -0,0 +1,9 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_anyof", + "value": { + "$ref": "#/components/schemas/SubType" + } + } +] diff --git a/tests/cases/simple/patch/oneof-to-allof.json b/tests/cases/simple/patch/oneof-to-allof.json new file mode 100644 index 0000000..f5e1433 --- /dev/null +++ b/tests/cases/simple/patch/oneof-to-allof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_oneof", + "value": { + "description": "Via allOf.", + "allOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/oneof-to-anyof.json b/tests/cases/simple/patch/oneof-to-anyof.json new file mode 100644 index 0000000..5ff8d9f --- /dev/null +++ b/tests/cases/simple/patch/oneof-to-anyof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_oneof", + "value": { + "description": "Via anyOf.", + "anyOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/oneof-to-ref.json b/tests/cases/simple/patch/oneof-to-ref.json new file mode 100644 index 0000000..6733d03 --- /dev/null +++ b/tests/cases/simple/patch/oneof-to-ref.json @@ -0,0 +1,9 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_oneof", + "value": { + "$ref": "#/components/schemas/SubType" + } + } +] diff --git a/tests/cases/simple/patch/ref-to-allof.json b/tests/cases/simple/patch/ref-to-allof.json new file mode 100644 index 0000000..aa4ff22 --- /dev/null +++ b/tests/cases/simple/patch/ref-to-allof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_ref", + "value": { + "description": "Via allOf.", + "allOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/ref-to-anyof.json b/tests/cases/simple/patch/ref-to-anyof.json new file mode 100644 index 0000000..e2da503 --- /dev/null +++ b/tests/cases/simple/patch/ref-to-anyof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_ref", + "value": { + "description": "Via anyOf.", + "anyOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/ref-to-oneof.json b/tests/cases/simple/patch/ref-to-oneof.json new file mode 100644 index 0000000..8df1cec --- /dev/null +++ b/tests/cases/simple/patch/ref-to-oneof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_ref", + "value": { + "description": "Via oneOf.", + "oneOf": [ + { + "$ref": "#/components/schemas/SubType" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/wrapper-unchanged-with-type-change.json b/tests/cases/simple/patch/wrapper-unchanged-with-type-change.json new file mode 100644 index 0000000..efd7b78 --- /dev/null +++ b/tests/cases/simple/patch/wrapper-unchanged-with-type-change.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/components/schemas/SubType/properties/value/type", + "value": "integer" + } +] From fbf0e7fd64b5a995c2deca8af53b44c8fa23b7f9 Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 9 Dec 2025 01:04:11 +0000 Subject: [PATCH 2/3] feedback Created using spr 1.3.6-beta.1 --- src/schema.rs | 89 ++++++++++--------- tests/cases/simple/base.json | 5 ++ tests/cases/simple/output/add-operation.out | 2 +- .../simple/output/add-type-extension.out | 2 +- tests/cases/simple/output/allof-to-anyof.out | 4 +- .../allof-to-oneof-with-type-change.out | 6 +- tests/cases/simple/output/allof-to-oneof.out | 4 +- .../output/allof-to-ref-with-type-change.out | 6 +- tests/cases/simple/output/allof-to-ref.out | 4 +- tests/cases/simple/output/anyof-to-allof.out | 2 +- tests/cases/simple/output/anyof-to-oneof.out | 2 +- tests/cases/simple/output/anyof-to-ref.out | 2 +- ...change-operation-parameter-requirement.out | 2 +- .../change-operation-parameter-type.out | 2 +- .../simple/output/change-property-type.out | 4 +- tests/cases/simple/output/inline-to-allof.out | 33 +++++++ .../cases/simple/output/modify-cycle-type.out | 2 +- .../simple/output/not-inner-to-allof.out | 35 ++++++++ tests/cases/simple/output/not-to-allof.out | 39 ++++++++ tests/cases/simple/output/oneof-to-allof.out | 2 +- tests/cases/simple/output/oneof-to-anyof.out | 2 +- tests/cases/simple/output/oneof-to-ref.out | 2 +- tests/cases/simple/output/ref-to-allof.out | 2 +- tests/cases/simple/output/ref-to-anyof.out | 2 +- .../simple/output/ref-to-inline-allof.out | 56 ++++++++++++ tests/cases/simple/output/ref-to-oneof.out | 2 +- .../output/remove-operation-parameter.out | 2 +- .../cases/simple/output/remove-operation.out | 2 +- .../cases/simple/output/type-indirection.out | 6 +- tests/cases/simple/output/type-rename.out | 2 +- .../simple/output/unhandled-add-prop.out | 8 +- .../wrapper-unchanged-with-type-change.out | 2 +- tests/cases/simple/patch/inline-to-allof.json | 14 +++ .../simple/patch/not-inner-to-allof.json | 16 ++++ tests/cases/simple/patch/not-to-allof.json | 16 ++++ .../simple/patch/ref-to-inline-allof.json | 20 +++++ 36 files changed, 318 insertions(+), 83 deletions(-) create mode 100644 tests/cases/simple/output/inline-to-allof.out create mode 100644 tests/cases/simple/output/not-inner-to-allof.out create mode 100644 tests/cases/simple/output/not-to-allof.out create mode 100644 tests/cases/simple/output/ref-to-inline-allof.out create mode 100644 tests/cases/simple/patch/inline-to-allof.json create mode 100644 tests/cases/simple/patch/not-inner-to-allof.json create mode 100644 tests/cases/simple/patch/not-to-allof.json create mode 100644 tests/cases/simple/patch/ref-to-inline-allof.json diff --git a/src/schema.rs b/src/schema.rs index 5bfdef2..99c8398 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -44,14 +44,17 @@ impl Compare { old_schema: Contextual<'_, &ReferenceOr>, new_schema: Contextual<'_, &ReferenceOr>, ) -> anyhow::Result { - // Handle single-element wrapper equivalence (allOf/anyOf/oneOf with one - // item). These are semantically equivalent to their inner type. + // Handle single-element wrappers: allOf/anyOf/oneOf with one item. + // These are semantically equivalent to their inner type. + // + // An allOf wrapper is commonly added to include additional metadata + // such as an additional description field. if let Some(result) = - self.try_compare_unwrapped(dry_run, comparison, &old_schema, &new_schema)? + self.try_compare_flattened(dry_run, comparison, &old_schema, &new_schema)? { Ok(result) } else { - // Normal path: resolve and compare. + // General path: resolve and compare. let (old_schema, old_context) = old_schema.contextual_resolve()?; let (new_schema, new_context) = new_schema.contextual_resolve()?; @@ -62,11 +65,11 @@ impl Compare { } } - /// Try to compare schemas by unwrapping single-element wrappers. + /// Try to compare schemas by flattening single-element wrappers. /// - /// Returns `Some(result)` if unwrapping was applicable, `None` to fall - /// through. - fn try_compare_unwrapped( + /// Returns `Some(result)` if flattening was applicable, `None` to fall + /// through to the general path. + fn try_compare_flattened( &mut self, dry_run: bool, comparison: SchemaComparison, @@ -79,7 +82,6 @@ impl Compare { let new_kind = classify_schema_ref(new_schema.as_ref()); match (old_kind, new_kind) { - // Both are single-element wrappers. Compare metadata and recurse ( SingleElement { inner: old_inner, @@ -90,6 +92,7 @@ impl Compare { metadata: new_meta, }, ) => { + // Both old and new are single-element wrappers. if old_meta != new_meta { self.push_change( "schema metadata changed", @@ -106,14 +109,15 @@ impl Compare { dry_run, comparison, old_inner, new_inner, )?)) } - // Old is single-element wrapper, new is bare ref. ( SingleElement { inner: old_inner, metadata: old_meta, }, - BareRef, + BareRef | InlineType, ) => { + // Old is a single-element wrapper, new is a bare ref or inline + // type. if has_meaningful_metadata(old_meta) { self.push_change( "schema metadata changed", @@ -132,14 +136,15 @@ impl Compare { new_schema.clone(), )?)) } - // Old is bare ref, new is single-element wrapper. ( - BareRef, + BareRef | InlineType, SingleElement { inner: new_inner, metadata: new_meta, }, ) => { + // Old is a bare ref or inline type, new is a single-element + // wrapper. if has_meaningful_metadata(new_meta) { self.push_change( "schema metadata changed", @@ -158,13 +163,13 @@ impl Compare { new_inner, )?)) } - // No unwrapping applicable - fall through to normal comparison. - (BareRef, BareRef) - | (BareRef, Other) - | (Other, BareRef) - | (Other, Other) - | (SingleElement { .. }, Other) - | (Other, SingleElement { .. }) => Ok(None), + (BareRef | InlineType | MultiElement, BareRef | InlineType | MultiElement) + | (SingleElement { .. }, MultiElement) + | (MultiElement, SingleElement { .. }) => { + // No flattening applicable, so fall through to the general + // comparison path. + Ok(None) + } } } @@ -332,19 +337,9 @@ impl Compare { openapiv3::SchemaKind::Not { not: old_not }, openapiv3::SchemaKind::Not { not: new_not }, ) => { - if old_not != new_not { - self.schema_push_change( - dry_run, - "unhandled, 'not' schema", - &old_schema_kind, - &new_schema_kind, - comparison, - ChangeClass::Unhandled, - ChangeDetails::UnknownDifference, - ) - } else { - Ok(true) - } + let old_not = old_schema_kind.append_deref(old_not.as_ref(), "not"); + let new_not = new_schema_kind.append_deref(new_not.as_ref(), "not"); + self.compare_schema_ref_helper(dry_run, comparison, old_not, new_not) } (&openapiv3::SchemaKind::Any(old_any), &openapiv3::SchemaKind::Any(new_any)) => { if old_any == new_any { @@ -826,25 +821,33 @@ impl SchemaKindTag { } } -/// Classification of a schema reference for wrapper unwrapping purposes. +/// Classification of a schema reference for flattening purposes. enum SchemaRefKind<'a> { - /// A bare $ref - can be compared with single-element wrappers. + /// A bare $ref. BareRef, - /// A single-element allOf/anyOf/oneOf wrapper, semantically equivalent to - /// the inner type. + /// An inline type (Type, Any, Not). + /// + /// It is okay to compare something like Not with single-element wrappers. + /// When recursing, we'll ensure that the child is also Not. + InlineType, + /// A single-element allOf/anyOf/oneOf wrapper that can be flattened to its + /// inner type. SingleElement { inner: &'a ReferenceOr, metadata: &'a SchemaData, }, - /// Something else (multi-element wrapper or non-wrapper type). - Other, + /// Multi-element allOf/anyOf/oneOf: cannot be flattened. + MultiElement, } -/// Classify a schema reference for wrapper unwrapping purposes. +/// Classify a schema reference for flattening purposes. fn classify_schema_ref(schema_ref: &ReferenceOr) -> SchemaRefKind<'_> { match schema_ref { ReferenceOr::Reference { .. } => SchemaRefKind::BareRef, ReferenceOr::Item(schema) => match &schema.schema_kind { + openapiv3::SchemaKind::Type(_) + | openapiv3::SchemaKind::Not { .. } + | openapiv3::SchemaKind::Any(_) => SchemaRefKind::InlineType, openapiv3::SchemaKind::AllOf { all_of } if all_of.len() == 1 => { SchemaRefKind::SingleElement { inner: all_of.first().unwrap(), @@ -863,12 +866,10 @@ fn classify_schema_ref(schema_ref: &ReferenceOr) -> SchemaRefKind<'_> { metadata: &schema.schema_data, } } + // Multi-element wrappers - not semantically equivalent to single schemas. openapiv3::SchemaKind::AllOf { .. } | openapiv3::SchemaKind::AnyOf { .. } - | openapiv3::SchemaKind::OneOf { .. } - | openapiv3::SchemaKind::Type(_) - | openapiv3::SchemaKind::Not { .. } - | openapiv3::SchemaKind::Any(_) => SchemaRefKind::Other, + | openapiv3::SchemaKind::OneOf { .. } => SchemaRefKind::MultiElement, }, } } diff --git a/tests/cases/simple/base.json b/tests/cases/simple/base.json index 0031433..6b7b876 100644 --- a/tests/cases/simple/base.json +++ b/tests/cases/simple/base.json @@ -107,6 +107,11 @@ "$ref": "#/components/schemas/SubType" } ] + }, + "not_a_number": { + "not": { + "type": "number" + } } }, "required": [ diff --git a/tests/cases/simple/output/add-operation.out b/tests/cases/simple/output/add-operation.out index 6b20975..e1aa2c1 100644 --- a/tests/cases/simple/output/add-operation.out +++ b/tests/cases/simple/output/add-operation.out @@ -1,6 +1,6 @@ --- add-operation.json +++ patched -@@ -68,6 +68,16 @@ +@@ -73,6 +73,16 @@ }, "openapi": "3.0.0", "paths": { diff --git a/tests/cases/simple/output/add-type-extension.out b/tests/cases/simple/output/add-type-extension.out index 01ae654..043ebc5 100644 --- a/tests/cases/simple/output/add-type-extension.out +++ b/tests/cases/simple/output/add-type-extension.out @@ -1,6 +1,6 @@ --- add-type-extension.json +++ patched -@@ -38,7 +38,10 @@ +@@ -43,7 +43,10 @@ "required": [ "message" ], diff --git a/tests/cases/simple/output/allof-to-anyof.out b/tests/cases/simple/output/allof-to-anyof.out index c3b8683..340e4c8 100644 --- a/tests/cases/simple/output/allof-to-anyof.out +++ b/tests/cases/simple/output/allof-to-anyof.out @@ -1,7 +1,7 @@ --- allof-to-anyof.json +++ patched -@@ -8,12 +8,12 @@ - "type": "string" +@@ -13,12 +13,12 @@ + } }, "via_allof": { - "allOf": [ diff --git a/tests/cases/simple/output/allof-to-oneof-with-type-change.out b/tests/cases/simple/output/allof-to-oneof-with-type-change.out index f39c8e9..9f7fe5b 100644 --- a/tests/cases/simple/output/allof-to-oneof-with-type-change.out +++ b/tests/cases/simple/output/allof-to-oneof-with-type-change.out @@ -1,7 +1,7 @@ --- allof-to-oneof-with-type-change.json +++ patched -@@ -8,12 +8,12 @@ - "type": "string" +@@ -13,12 +13,12 @@ + } }, "via_allof": { - "allOf": [ @@ -16,7 +16,7 @@ }, "via_anyof": { "anyOf": [ -@@ -43,7 +43,7 @@ +@@ -48,7 +48,7 @@ "SubType": { "properties": { "value": { diff --git a/tests/cases/simple/output/allof-to-oneof.out b/tests/cases/simple/output/allof-to-oneof.out index 55d9a71..a9da088 100644 --- a/tests/cases/simple/output/allof-to-oneof.out +++ b/tests/cases/simple/output/allof-to-oneof.out @@ -1,7 +1,7 @@ --- allof-to-oneof.json +++ patched -@@ -8,12 +8,12 @@ - "type": "string" +@@ -13,12 +13,12 @@ + } }, "via_allof": { - "allOf": [ diff --git a/tests/cases/simple/output/allof-to-ref-with-type-change.out b/tests/cases/simple/output/allof-to-ref-with-type-change.out index 377b449..7a85e36 100644 --- a/tests/cases/simple/output/allof-to-ref-with-type-change.out +++ b/tests/cases/simple/output/allof-to-ref-with-type-change.out @@ -1,7 +1,7 @@ --- allof-to-ref-with-type-change.json +++ patched -@@ -8,12 +8,7 @@ - "type": "string" +@@ -13,12 +13,7 @@ + } }, "via_allof": { - "allOf": [ @@ -14,7 +14,7 @@ }, "via_anyof": { "anyOf": [ -@@ -42,6 +37,9 @@ +@@ -47,6 +42,9 @@ }, "SubType": { "properties": { diff --git a/tests/cases/simple/output/allof-to-ref.out b/tests/cases/simple/output/allof-to-ref.out index ad04101..613b85a 100644 --- a/tests/cases/simple/output/allof-to-ref.out +++ b/tests/cases/simple/output/allof-to-ref.out @@ -1,7 +1,7 @@ --- allof-to-ref.json +++ patched -@@ -8,12 +8,7 @@ - "type": "string" +@@ -13,12 +13,7 @@ + } }, "via_allof": { - "allOf": [ diff --git a/tests/cases/simple/output/anyof-to-allof.out b/tests/cases/simple/output/anyof-to-allof.out index 66580d6..3b79c72 100644 --- a/tests/cases/simple/output/anyof-to-allof.out +++ b/tests/cases/simple/output/anyof-to-allof.out @@ -1,6 +1,6 @@ --- anyof-to-allof.json +++ patched -@@ -16,12 +16,12 @@ +@@ -21,12 +21,12 @@ "description": "Via allOf." }, "via_anyof": { diff --git a/tests/cases/simple/output/anyof-to-oneof.out b/tests/cases/simple/output/anyof-to-oneof.out index baade56..30f4d9e 100644 --- a/tests/cases/simple/output/anyof-to-oneof.out +++ b/tests/cases/simple/output/anyof-to-oneof.out @@ -1,6 +1,6 @@ --- anyof-to-oneof.json +++ patched -@@ -16,12 +16,12 @@ +@@ -21,12 +21,12 @@ "description": "Via allOf." }, "via_anyof": { diff --git a/tests/cases/simple/output/anyof-to-ref.out b/tests/cases/simple/output/anyof-to-ref.out index c2c8fad..7b30a80 100644 --- a/tests/cases/simple/output/anyof-to-ref.out +++ b/tests/cases/simple/output/anyof-to-ref.out @@ -1,6 +1,6 @@ --- anyof-to-ref.json +++ patched -@@ -16,12 +16,7 @@ +@@ -21,12 +21,7 @@ "description": "Via allOf." }, "via_anyof": { diff --git a/tests/cases/simple/output/change-operation-parameter-requirement.out b/tests/cases/simple/output/change-operation-parameter-requirement.out index b00c292..4cb3afe 100644 --- a/tests/cases/simple/output/change-operation-parameter-requirement.out +++ b/tests/cases/simple/output/change-operation-parameter-requirement.out @@ -1,6 +1,6 @@ --- change-operation-parameter-requirement.json +++ patched -@@ -83,7 +83,7 @@ +@@ -88,7 +88,7 @@ "description": "Language for the greeting", "in": "query", "name": "language", diff --git a/tests/cases/simple/output/change-operation-parameter-type.out b/tests/cases/simple/output/change-operation-parameter-type.out index 0ca2ac7..a37355d 100644 --- a/tests/cases/simple/output/change-operation-parameter-type.out +++ b/tests/cases/simple/output/change-operation-parameter-type.out @@ -1,6 +1,6 @@ --- change-operation-parameter-type.json +++ patched -@@ -85,7 +85,7 @@ +@@ -90,7 +90,7 @@ "name": "language", "required": false, "schema": { diff --git a/tests/cases/simple/output/change-property-type.out b/tests/cases/simple/output/change-property-type.out index 136564f..849276a 100644 --- a/tests/cases/simple/output/change-property-type.out +++ b/tests/cases/simple/output/change-property-type.out @@ -7,8 +7,8 @@ - "type": "string" + "type": "integer" }, - "via_allof": { - "allOf": [ + "not_a_number": { + "not": { Result for patch: diff --git a/tests/cases/simple/output/inline-to-allof.out b/tests/cases/simple/output/inline-to-allof.out new file mode 100644 index 0000000..ac4de91 --- /dev/null +++ b/tests/cases/simple/output/inline-to-allof.out @@ -0,0 +1,33 @@ +--- inline-to-allof.json ++++ patched +@@ -81,7 +81,12 @@ + "in": "path", + "name": "name", + "schema": { +- "type": "string" ++ "allOf": [ ++ { ++ "type": "string" ++ } ++ ], ++ "description": "A string parameter" + } + }, + { + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/paths/~1hello~1{name}/get/parameters/0/schema", + ], + new_path: [ + "#/paths/~1hello~1{name}/get/parameters/0/schema", + ], + comparison: Input, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/modify-cycle-type.out b/tests/cases/simple/output/modify-cycle-type.out index 3414d50..2048834 100644 --- a/tests/cases/simple/output/modify-cycle-type.out +++ b/tests/cases/simple/output/modify-cycle-type.out @@ -1,6 +1,6 @@ --- modify-cycle-type.json +++ patched -@@ -51,10 +51,7 @@ +@@ -56,10 +56,7 @@ "Tree": { "properties": { "children": { diff --git a/tests/cases/simple/output/not-inner-to-allof.out b/tests/cases/simple/output/not-inner-to-allof.out new file mode 100644 index 0000000..ab9baee --- /dev/null +++ b/tests/cases/simple/output/not-inner-to-allof.out @@ -0,0 +1,35 @@ +--- not-inner-to-allof.json ++++ patched +@@ -9,7 +9,12 @@ + }, + "not_a_number": { + "not": { +- "type": "number" ++ "allOf": [ ++ { ++ "type": "number" ++ } ++ ], ++ "description": "A number type" + } + }, + "via_allof": { + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/not_a_number/not", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/not_a_number/not", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/not-to-allof.out b/tests/cases/simple/output/not-to-allof.out new file mode 100644 index 0000000..4c8f668 --- /dev/null +++ b/tests/cases/simple/output/not-to-allof.out @@ -0,0 +1,39 @@ +--- not-to-allof.json ++++ patched +@@ -8,9 +8,14 @@ + "type": "string" + }, + "not_a_number": { +- "not": { +- "type": "number" +- } ++ "allOf": [ ++ { ++ "not": { ++ "type": "number" ++ } ++ } ++ ], ++ "description": "Not a number, wrapped in allOf" + }, + "via_allof": { + "allOf": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/not_a_number", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/not_a_number", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/oneof-to-allof.out b/tests/cases/simple/output/oneof-to-allof.out index ed58fb9..b67b769 100644 --- a/tests/cases/simple/output/oneof-to-allof.out +++ b/tests/cases/simple/output/oneof-to-allof.out @@ -1,6 +1,6 @@ --- oneof-to-allof.json +++ patched -@@ -24,12 +24,12 @@ +@@ -29,12 +29,12 @@ "description": "Via anyOf." }, "via_oneof": { diff --git a/tests/cases/simple/output/oneof-to-anyof.out b/tests/cases/simple/output/oneof-to-anyof.out index aa360f6..fcd4fa2 100644 --- a/tests/cases/simple/output/oneof-to-anyof.out +++ b/tests/cases/simple/output/oneof-to-anyof.out @@ -1,6 +1,6 @@ --- oneof-to-anyof.json +++ patched -@@ -24,12 +24,12 @@ +@@ -29,12 +29,12 @@ "description": "Via anyOf." }, "via_oneof": { diff --git a/tests/cases/simple/output/oneof-to-ref.out b/tests/cases/simple/output/oneof-to-ref.out index ca77305..9bbdf25 100644 --- a/tests/cases/simple/output/oneof-to-ref.out +++ b/tests/cases/simple/output/oneof-to-ref.out @@ -1,6 +1,6 @@ --- oneof-to-ref.json +++ patched -@@ -24,12 +24,7 @@ +@@ -29,12 +29,7 @@ "description": "Via anyOf." }, "via_oneof": { diff --git a/tests/cases/simple/output/ref-to-allof.out b/tests/cases/simple/output/ref-to-allof.out index 63a17f0..a5359ce 100644 --- a/tests/cases/simple/output/ref-to-allof.out +++ b/tests/cases/simple/output/ref-to-allof.out @@ -1,6 +1,6 @@ --- ref-to-allof.json +++ patched -@@ -32,7 +32,12 @@ +@@ -37,7 +37,12 @@ ] }, "via_ref": { diff --git a/tests/cases/simple/output/ref-to-anyof.out b/tests/cases/simple/output/ref-to-anyof.out index 0559391..173e9ae 100644 --- a/tests/cases/simple/output/ref-to-anyof.out +++ b/tests/cases/simple/output/ref-to-anyof.out @@ -1,6 +1,6 @@ --- ref-to-anyof.json +++ patched -@@ -32,7 +32,12 @@ +@@ -37,7 +37,12 @@ ] }, "via_ref": { diff --git a/tests/cases/simple/output/ref-to-inline-allof.out b/tests/cases/simple/output/ref-to-inline-allof.out new file mode 100644 index 0000000..e2b2c73 --- /dev/null +++ b/tests/cases/simple/output/ref-to-inline-allof.out @@ -0,0 +1,56 @@ +--- ref-to-inline-allof.json ++++ patched +@@ -37,7 +37,18 @@ + ] + }, + "via_ref": { +- "$ref": "#/components/schemas/SubType" ++ "allOf": [ ++ { ++ "description": "Greeting response type", ++ "properties": { ++ "value": { ++ "type": "string" ++ } ++ }, ++ "type": "object" ++ } ++ ], ++ "description": "A greeting response" + } + }, + "required": [ + + +Result for patch: +[ + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, + Change { + message: "schema metadata changed", + old_path: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_ref/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new_path: [ + "#/components/schemas/GreetingResponse/properties/via_ref/0", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + class: Trivial, + details: Metadata, + }, +] diff --git a/tests/cases/simple/output/ref-to-oneof.out b/tests/cases/simple/output/ref-to-oneof.out index 9219537..3fb0d11 100644 --- a/tests/cases/simple/output/ref-to-oneof.out +++ b/tests/cases/simple/output/ref-to-oneof.out @@ -1,6 +1,6 @@ --- ref-to-oneof.json +++ patched -@@ -32,7 +32,12 @@ +@@ -37,7 +37,12 @@ ] }, "via_ref": { diff --git a/tests/cases/simple/output/remove-operation-parameter.out b/tests/cases/simple/output/remove-operation-parameter.out index 7001216..f327dd7 100644 --- a/tests/cases/simple/output/remove-operation-parameter.out +++ b/tests/cases/simple/output/remove-operation-parameter.out @@ -1,6 +1,6 @@ --- remove-operation-parameter.json +++ patched -@@ -78,15 +78,6 @@ +@@ -83,15 +83,6 @@ "schema": { "type": "string" } diff --git a/tests/cases/simple/output/remove-operation.out b/tests/cases/simple/output/remove-operation.out index 57c225b..87f6495 100644 --- a/tests/cases/simple/output/remove-operation.out +++ b/tests/cases/simple/output/remove-operation.out @@ -1,6 +1,6 @@ --- remove-operation.json +++ patched -@@ -104,18 +104,7 @@ +@@ -109,18 +109,7 @@ "summary": "Say hello" } }, diff --git a/tests/cases/simple/output/type-indirection.out b/tests/cases/simple/output/type-indirection.out index 44cfe9b..07df301 100644 --- a/tests/cases/simple/output/type-indirection.out +++ b/tests/cases/simple/output/type-indirection.out @@ -8,9 +8,9 @@ - "type": "string" + "$ref": "#/components/schemas/GreetingResponseMessage" }, - "via_allof": { - "allOf": [ -@@ -40,6 +39,13 @@ + "not_a_number": { + "not": { +@@ -45,6 +44,13 @@ ], "type": "object" }, diff --git a/tests/cases/simple/output/type-rename.out b/tests/cases/simple/output/type-rename.out index e1226df..df8d69b 100644 --- a/tests/cases/simple/output/type-rename.out +++ b/tests/cases/simple/output/type-rename.out @@ -9,7 +9,7 @@ "properties": { "message": { "description": "The greeting message", -@@ -94,7 +94,7 @@ +@@ -99,7 +99,7 @@ "content": { "application/json": { "schema": { diff --git a/tests/cases/simple/output/unhandled-add-prop.out b/tests/cases/simple/output/unhandled-add-prop.out index 361e725..231da94 100644 --- a/tests/cases/simple/output/unhandled-add-prop.out +++ b/tests/cases/simple/output/unhandled-add-prop.out @@ -1,8 +1,8 @@ --- unhandled-add-prop.json +++ patched -@@ -7,6 +7,10 @@ - "description": "The greeting message", - "type": "string" +@@ -12,6 +12,10 @@ + "type": "number" + } }, + "source": { + "description": "Where the greeting originated", @@ -11,7 +11,7 @@ "via_allof": { "allOf": [ { -@@ -36,7 +40,8 @@ +@@ -41,7 +45,8 @@ } }, "required": [ diff --git a/tests/cases/simple/output/wrapper-unchanged-with-type-change.out b/tests/cases/simple/output/wrapper-unchanged-with-type-change.out index 6d0b478..14a036b 100644 --- a/tests/cases/simple/output/wrapper-unchanged-with-type-change.out +++ b/tests/cases/simple/output/wrapper-unchanged-with-type-change.out @@ -1,6 +1,6 @@ --- wrapper-unchanged-with-type-change.json +++ patched -@@ -43,7 +43,7 @@ +@@ -48,7 +48,7 @@ "SubType": { "properties": { "value": { diff --git a/tests/cases/simple/patch/inline-to-allof.json b/tests/cases/simple/patch/inline-to-allof.json new file mode 100644 index 0000000..8efe6df --- /dev/null +++ b/tests/cases/simple/patch/inline-to-allof.json @@ -0,0 +1,14 @@ +[ + { + "op": "replace", + "path": "/paths/~1hello~1{name}/get/parameters/0/schema", + "value": { + "description": "A string parameter", + "allOf": [ + { + "type": "string" + } + ] + } + } +] diff --git a/tests/cases/simple/patch/not-inner-to-allof.json b/tests/cases/simple/patch/not-inner-to-allof.json new file mode 100644 index 0000000..cbef6d6 --- /dev/null +++ b/tests/cases/simple/patch/not-inner-to-allof.json @@ -0,0 +1,16 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/not_a_number", + "value": { + "not": { + "description": "A number type", + "allOf": [ + { + "type": "number" + } + ] + } + } + } +] diff --git a/tests/cases/simple/patch/not-to-allof.json b/tests/cases/simple/patch/not-to-allof.json new file mode 100644 index 0000000..cffcccf --- /dev/null +++ b/tests/cases/simple/patch/not-to-allof.json @@ -0,0 +1,16 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/not_a_number", + "value": { + "description": "Not a number, wrapped in allOf", + "allOf": [ + { + "not": { + "type": "number" + } + } + ] + } + } +] diff --git a/tests/cases/simple/patch/ref-to-inline-allof.json b/tests/cases/simple/patch/ref-to-inline-allof.json new file mode 100644 index 0000000..de10b3c --- /dev/null +++ b/tests/cases/simple/patch/ref-to-inline-allof.json @@ -0,0 +1,20 @@ +[ + { + "op": "replace", + "path": "/components/schemas/GreetingResponse/properties/via_ref", + "value": { + "description": "A greeting response", + "allOf": [ + { + "type": "object", + "description": "Greeting response type", + "properties": { + "value": { + "type": "string" + } + } + } + ] + } + } +] From 2d8d379d8f9da3d6b788782b84fb61f37e7e499b Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 9 Dec 2025 01:31:19 +0000 Subject: [PATCH 3/3] comments + update to 'schema metadata removed/added' Created using spr 1.3.6-beta.1 --- src/schema.rs | 13 ++++++++++--- .../simple/output/allof-to-ref-with-type-change.out | 2 +- tests/cases/simple/output/allof-to-ref.out | 2 +- tests/cases/simple/output/anyof-to-ref.out | 2 +- tests/cases/simple/output/inline-to-allof.out | 2 +- tests/cases/simple/output/not-inner-to-allof.out | 2 +- tests/cases/simple/output/not-to-allof.out | 2 +- tests/cases/simple/output/oneof-to-ref.out | 2 +- tests/cases/simple/output/ref-to-allof.out | 2 +- tests/cases/simple/output/ref-to-anyof.out | 2 +- tests/cases/simple/output/ref-to-inline-allof.out | 2 +- tests/cases/simple/output/ref-to-oneof.out | 2 +- 12 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/schema.rs b/src/schema.rs index 99c8398..8dc849d 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -118,9 +118,12 @@ impl Compare { ) => { // Old is a single-element wrapper, new is a bare ref or inline // type. + // + // A bare ref or inline type does not have metadata, so if the + // old metadata is non-default, report a trivial change. if has_meaningful_metadata(old_meta) { self.push_change( - "schema metadata changed", + "schema metadata removed", old_schema, new_schema, comparison.into(), @@ -145,9 +148,12 @@ impl Compare { ) => { // Old is a bare ref or inline type, new is a single-element // wrapper. + // + // A bare ref or inline type does not have metadata, so if the + // new metadata is non-default, report a trivial change. if has_meaningful_metadata(new_meta) { self.push_change( - "schema metadata changed", + "schema metadata added", old_schema, new_schema, comparison.into(), @@ -836,7 +842,8 @@ enum SchemaRefKind<'a> { inner: &'a ReferenceOr, metadata: &'a SchemaData, }, - /// Multi-element allOf/anyOf/oneOf: cannot be flattened. + /// Multi (or, less commonly, zero) element allOf/anyOf/oneOf: cannot be + /// flattened. MultiElement, } diff --git a/tests/cases/simple/output/allof-to-ref-with-type-change.out b/tests/cases/simple/output/allof-to-ref-with-type-change.out index 7a85e36..5d46eca 100644 --- a/tests/cases/simple/output/allof-to-ref-with-type-change.out +++ b/tests/cases/simple/output/allof-to-ref-with-type-change.out @@ -29,7 +29,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata removed", old_path: [ "#/components/schemas/GreetingResponse/properties/via_allof", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/allof-to-ref.out b/tests/cases/simple/output/allof-to-ref.out index 613b85a..208cd01 100644 --- a/tests/cases/simple/output/allof-to-ref.out +++ b/tests/cases/simple/output/allof-to-ref.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata removed", old_path: [ "#/components/schemas/GreetingResponse/properties/via_allof", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/anyof-to-ref.out b/tests/cases/simple/output/anyof-to-ref.out index 7b30a80..c1a38bd 100644 --- a/tests/cases/simple/output/anyof-to-ref.out +++ b/tests/cases/simple/output/anyof-to-ref.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata removed", old_path: [ "#/components/schemas/GreetingResponse/properties/via_anyof", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/inline-to-allof.out b/tests/cases/simple/output/inline-to-allof.out index ac4de91..990a799 100644 --- a/tests/cases/simple/output/inline-to-allof.out +++ b/tests/cases/simple/output/inline-to-allof.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/paths/~1hello~1{name}/get/parameters/0/schema", ], diff --git a/tests/cases/simple/output/not-inner-to-allof.out b/tests/cases/simple/output/not-inner-to-allof.out index ab9baee..98295ff 100644 --- a/tests/cases/simple/output/not-inner-to-allof.out +++ b/tests/cases/simple/output/not-inner-to-allof.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/components/schemas/GreetingResponse/properties/not_a_number/not", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/not-to-allof.out b/tests/cases/simple/output/not-to-allof.out index 4c8f668..a088c44 100644 --- a/tests/cases/simple/output/not-to-allof.out +++ b/tests/cases/simple/output/not-to-allof.out @@ -23,7 +23,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/components/schemas/GreetingResponse/properties/not_a_number", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/oneof-to-ref.out b/tests/cases/simple/output/oneof-to-ref.out index 9bbdf25..b173144 100644 --- a/tests/cases/simple/output/oneof-to-ref.out +++ b/tests/cases/simple/output/oneof-to-ref.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata removed", old_path: [ "#/components/schemas/GreetingResponse/properties/via_oneof", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/ref-to-allof.out b/tests/cases/simple/output/ref-to-allof.out index a5359ce..b02467c 100644 --- a/tests/cases/simple/output/ref-to-allof.out +++ b/tests/cases/simple/output/ref-to-allof.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/components/schemas/GreetingResponse/properties/via_ref", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/ref-to-anyof.out b/tests/cases/simple/output/ref-to-anyof.out index 173e9ae..fe43f29 100644 --- a/tests/cases/simple/output/ref-to-anyof.out +++ b/tests/cases/simple/output/ref-to-anyof.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/components/schemas/GreetingResponse/properties/via_ref", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/ref-to-inline-allof.out b/tests/cases/simple/output/ref-to-inline-allof.out index e2b2c73..fb1fd7b 100644 --- a/tests/cases/simple/output/ref-to-inline-allof.out +++ b/tests/cases/simple/output/ref-to-inline-allof.out @@ -25,7 +25,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/components/schemas/GreetingResponse/properties/via_ref", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", diff --git a/tests/cases/simple/output/ref-to-oneof.out b/tests/cases/simple/output/ref-to-oneof.out index 3fb0d11..66f1a29 100644 --- a/tests/cases/simple/output/ref-to-oneof.out +++ b/tests/cases/simple/output/ref-to-oneof.out @@ -19,7 +19,7 @@ Result for patch: [ Change { - message: "schema metadata changed", + message: "schema metadata added", old_path: [ "#/components/schemas/GreetingResponse/properties/via_ref", "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref",