From 896ecda5fbc4c934a5a1ec928ba0cc3355750c45 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 6 Feb 2026 03:27:17 +0000 Subject: [PATCH] [spr] changes to main this commit is based on Created using spr 1.3.6-beta.1 [skip ci] --- src/change.rs | 68 +- src/compare.rs | 65 +- src/context.rs | 42 +- src/lib.rs | 71 +- src/operations.rs | 30 +- src/path.rs | 620 ++++++++++++++++++ src/resolve.rs | 5 +- src/schema.rs | 22 +- .../simple/output/add-cookie-parameter.out | 28 +- .../simple/output/add-default-response.out | 28 +- .../simple/output/add-header-parameter.out | 28 +- .../simple/output/add-operation-with-id.out | 28 +- tests/cases/simple/output/add-operation.out | 28 +- .../cases/simple/output/add-optional-body.out | 28 +- .../simple/output/add-optional-parameter.out | 28 +- .../cases/simple/output/add-required-body.out | 28 +- .../simple/output/add-required-parameter.out | 28 +- .../cases/simple/output/add-response-code.out | 28 +- .../simple/output/add-type-extension.out | 32 +- tests/cases/simple/output/allof-to-anyof.out | 32 +- .../allof-to-oneof-with-type-change.out | 68 +- tests/cases/simple/output/allof-to-oneof.out | 32 +- .../output/allof-to-ref-with-type-change.out | 68 +- tests/cases/simple/output/allof-to-ref.out | 32 +- .../cases/simple/output/any-schema-change.out | 32 +- tests/cases/simple/output/anyof-to-allof.out | 32 +- tests/cases/simple/output/anyof-to-oneof.out | 32 +- tests/cases/simple/output/anyof-to-ref.out | 32 +- tests/cases/simple/output/array-items-add.out | 32 +- .../simple/output/array-items-remove.out | 34 +- .../simple/output/array-max-items-change.out | 32 +- .../simple/output/array-min-items-change.out | 32 +- .../output/array-unique-items-change.out | 32 +- .../simple/output/body-description-change.out | 28 +- .../simple/output/body-extension-change.out | 28 +- .../output/body-optional-to-required.out | 28 +- .../output/body-required-to-optional.out | 28 +- tests/cases/simple/output/boolean-change.out | 32 +- .../simple/output/boolean-enum-change.out | 32 +- .../simple/output/change-default-response.out | 2 +- .../simple/output/change-header-parameter.out | 28 +- ...change-operation-parameter-requirement.out | 28 +- .../change-operation-parameter-type.out | 28 +- .../simple/output/change-property-type.out | 32 +- tests/cases/simple/output/inline-to-allof.out | 28 +- .../simple/output/integer-format-change.out | 32 +- .../cases/simple/output/modify-cycle-type.out | 34 +- .../output/multi-allof-variant-change.out | 32 +- .../output/multi-anyof-variant-change.out | 32 +- .../output/multi-oneof-count-change.out | 32 +- .../output/multi-oneof-variant-change.out | 32 +- .../simple/output/not-inner-to-allof.out | 32 +- tests/cases/simple/output/not-to-allof.out | 32 +- .../output/number-constraints-change.out | 32 +- .../output/object-additional-props-false.out | 32 +- .../object-additional-props-schema-change.out | 32 +- .../object-additional-props-type-change.out | 32 +- .../output/object-max-properties-change.out | 32 +- .../output/object-min-properties-change.out | 32 +- tests/cases/simple/output/oneof-to-allof.out | 32 +- tests/cases/simple/output/oneof-to-anyof.out | 32 +- tests/cases/simple/output/oneof-to-ref.out | 32 +- .../output/param-required-to-optional.out | 28 +- .../simple/output/param-schema-to-content.out | 28 +- .../cases/simple/output/ref-chain-change.out | 78 ++- tests/cases/simple/output/ref-to-allof.out | 32 +- tests/cases/simple/output/ref-to-anyof.out | 32 +- .../simple/output/ref-to-inline-allof.out | 64 +- tests/cases/simple/output/ref-to-oneof.out | 32 +- .../simple/output/remove-default-response.out | 28 +- .../simple/output/remove-header-parameter.out | 28 +- .../output/remove-operation-parameter.out | 28 +- .../cases/simple/output/remove-operation.out | 28 +- .../simple/output/remove-optional-body.out | 28 +- .../simple/output/remove-required-body.out | 28 +- .../simple/output/remove-response-code.out | 28 +- .../output/remove-unnamed-operation.out | 28 +- .../output/schema-kind-type-to-oneof.out | 36 +- .../simple/output/string-format-change.out | 32 +- .../cases/simple/output/type-indirection.out | 36 +- tests/cases/simple/output/type-rename.out | 4 +- .../simple/output/unhandled-add-prop.out | 64 +- .../wrapper-unchanged-with-type-change.out | 36 +- tests/test_changes.rs | 32 +- 84 files changed, 2422 insertions(+), 961 deletions(-) create mode 100644 src/path.rs diff --git a/src/change.rs b/src/change.rs index e29b3cf..d8b0974 100644 --- a/src/change.rs +++ b/src/change.rs @@ -1,9 +1,10 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company + +use std::fmt; use crate::JsonPathStack; // Describes any change detected between two OpenAPI documents. -#[derive(Debug)] pub struct Change { /// Human-readable message describing the nature of the change. pub message: String, @@ -18,10 +19,71 @@ pub struct Change { /// Classification of the change compatibility. pub class: ChangeClass, - /// Details on the kind of change + /// Details on the kind of change. pub details: ChangeDetails, } +// Format `Change` in the nested `paths`/`changes` layout that will be +// introduced when the type is restructured into `Change`, `ChangePath`, and +// `ChangeInfo`. Doing this ahead of time keeps the restructuring commit free +// of test-output noise. +impl fmt::Debug for Change { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// Wrapper that formats as `ChangePath { old, new, comparison }`. + struct PathFmt<'a> { + old: &'a JsonPathStack, + new: &'a JsonPathStack, + comparison: &'a ChangeComparison, + } + + impl fmt::Debug for PathFmt<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ChangePath") + .field("old", self.old) + .field("new", self.new) + .field("comparison", self.comparison) + .finish() + } + } + + /// Wrapper that formats as `ChangeInfo { old_subpath, new_subpath, + /// message, class, details }`. + struct InfoFmt<'a> { + message: &'a str, + class: &'a ChangeClass, + details: &'a ChangeDetails, + } + + impl fmt::Debug for InfoFmt<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ChangeInfo") + .field("old_subpath", &"") + .field("new_subpath", &"") + .field("message", &self.message) + .field("class", self.class) + .field("details", self.details) + .finish() + } + } + + let path = PathFmt { + old: &self.old_path, + new: &self.new_path, + comparison: &self.comparison, + }; + let info = InfoFmt { + message: &self.message, + class: &self.class, + details: &self.details, + }; + + f.debug_struct("Change") + .field("paths", &[path]) + .field("changes", &[info]) + .finish() + } +} + impl Change { pub fn new( message: impl ToString, diff --git a/src/compare.rs b/src/compare.rs index eb77743..a296768 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -1,7 +1,8 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::collections::BTreeMap; +use anyhow::Context as _; use indexmap::IndexMap; use openapiv3::{ MediaType, Operation, Parameter, ParameterSchemaOrContent, ReferenceOr, RequestBody, @@ -9,7 +10,7 @@ use openapiv3::{ use serde_json::Value; use crate::{ - Change, ChangeClass, ChangeComparison, ChangeDetails, + Change, ChangeClass, ChangeComparison, ChangeDetails, JsonPathStack, context::{Context, Contextual, ToContext}, operations::{all_params, operations}, resolve::ReferenceOrResolver, @@ -24,21 +25,58 @@ pub fn compare(old: &Value, new: &Value) -> anyhow::Result> { Ok(comp.changes) } +/// The full current location in a document, including appended segments. +/// +/// This is the path returned by `JsonPathStack::current_pointer()`, wrapped +/// in a newtype to prevent confusion with other path-like strings (e.g. a +/// future base path type that excludes appended segments). +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct CurrentPointer(String); + +impl CurrentPointer { + pub(crate) fn new(stack: &JsonPathStack) -> Self { + Self(stack.current_pointer().to_string()) + } +} + +/// Key for memoizing schema comparison results. +/// +/// This uses the full schema path (including internal paths like +/// `SubType/properties/value`) for accurate memoization. The comparison +/// direction (Input vs Output) is included because compatibility semantics +/// differ based on direction. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct VisitedKey { + comparison: SchemaComparison, + old_path: CurrentPointer, + new_path: CurrentPointer, +} + +impl VisitedKey { + pub(crate) fn new( + comparison: SchemaComparison, + old_stack: &JsonPathStack, + new_stack: &JsonPathStack, + ) -> Self { + Self { + comparison, + old_path: CurrentPointer::new(old_stack), + new_path: CurrentPointer::new(new_stack), + } + } +} + #[derive(Default)] pub(crate) struct Compare { pub changes: Vec, - /// Cache comparisons (type of comparison, old and new path to the schema) - /// and the boolean result (are the schemas backward-compatible?). - pub visited: BTreeMap<(SchemaComparison, String, String), bool>, + /// Memoization of schema comparison results, keyed by full path pair. + pub visited: BTreeMap, } impl Compare { pub fn compare(&mut self, old: &Value, new: &Value) -> anyhow::Result<()> { - let old_context = Context::new(old); - let old_operations = operations(&old_context)?; - - let new_context = Context::new(new); - let new_operations = operations(&new_context)?; + let old_operations = operations(old).context("error deserializing old OpenAPI document")?; + let new_operations = operations(new).context("error deserializing new OpenAPI document")?; let SetCompare { a_unique, @@ -52,10 +90,11 @@ impl Compare { Some(name) => name.as_str(), None => "", }; + let new_paths_root = Context::for_paths_root(new); self.push_change( format!("The operation {op_name} was removed"), &op_info.operation, - &new_context.append("paths"), + &new_paths_root, ChangeComparison::Structural, ChangeClass::BackwardIncompatible, ChangeDetails::Removed, @@ -68,10 +107,10 @@ impl Compare { Some(name) => name.as_str(), None => "", }; - + let old_paths_root = Context::for_paths_root(old); self.push_change( format!("The operation {op_name} was added"), - &old_context.append("paths"), + &old_paths_root, &op_info.operation, ChangeComparison::Structural, ChangeClass::ForwardIncompatible, diff --git a/src/context.rs b/src/context.rs index 04c6c7e..80b6830 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::{borrow::Cow, ops::Deref}; @@ -6,7 +6,10 @@ use openapiv3::ReferenceOr; use serde::de::DeserializeOwned; use serde_json::Value; -use crate::{JsonPathStack, resolve::ReferenceOrResolver}; +use crate::{ + path::{EndpointPath, InvalidComponentRef, JsonPathStack}, + resolve::ReferenceOrResolver, +}; #[derive(Clone, Debug)] pub struct Context<'a> { @@ -15,33 +18,42 @@ pub struct Context<'a> { } impl<'a> Context<'a> { - pub fn new(raw_openapi: &'a Value) -> Self { + /// Create a context at a specific endpoint. + /// + /// This is the normal way to create a context for schema comparison. + pub fn for_endpoint(raw_openapi: &'a Value, endpoint: EndpointPath) -> Self { Self { raw_openapi, - stack: JsonPathStack::new(), + stack: JsonPathStack::for_endpoint(endpoint), } } - pub fn append(&self, segment: &str) -> Context<'a> { - let stack = self - .stack - .append(&segment.replace("~", "~0").replace("/", "~1")); - + /// Create a context at `#/paths`. + /// + /// This is used only when reporting that an operation was added or removed, + /// where one side doesn't have the operation. In normal use, prefer + /// `for_endpoint()`. + pub fn for_paths_root(raw_openapi: &'a Value) -> Self { Self { - raw_openapi: self.raw_openapi, - stack, + raw_openapi, + stack: JsonPathStack::paths_root(), } } - pub(crate) fn push(&self, path: &str) -> Context<'a> { - let stack = self.stack.push(path); - + pub fn append(&self, segment: &str) -> Context<'a> { Self { raw_openapi: self.raw_openapi, - stack, + stack: self.stack.append(segment), } } + pub(crate) fn push(&self, path: &str) -> Result, InvalidComponentRef> { + Ok(Self { + raw_openapi: self.raw_openapi, + stack: self.stack.push(path)?, + }) + } + pub fn stack(&self) -> &JsonPathStack { &self.stack } diff --git a/src/lib.rs b/src/lib.rs index dbdbe3a..ac548b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Drift //! @@ -8,76 +8,11 @@ mod change; mod compare; mod context; mod operations; +mod path; mod resolve; mod schema; mod setops; -use std::fmt::Debug; - pub use change::*; pub use compare::compare; - -/// Represents a location in an OpenAPI document. -/// -/// This takes the the form of a stack of JSON paths where each element of the -/// stack starts at the document root and terminates in either a reference -/// (i.e. to the subsequent element in the stack) or the item being identified. -#[derive(Clone)] -pub struct JsonPathStack { - top: String, - stack: Vec, -} - -impl Debug for JsonPathStack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut out = f.debug_list(); - out.entry(&self.top); - self.stack.iter().rev().for_each(|path| { - let _ = out.entry(path); - }); - out.finish() - } -} - -impl JsonPathStack { - fn new() -> Self { - Self { - top: "#".to_string(), - stack: Vec::new(), - } - } - - fn append(&self, segment: &str) -> JsonPathStack { - let Self { top, stack } = self; - - Self { - top: format!("{top}/{segment}"), - stack: stack.clone(), - } - } - - fn push(&self, path: &str) -> JsonPathStack { - let Self { top, stack } = self; - let mut stack = stack.clone(); - stack.push(format!("{top}/$ref")); - - Self { - top: path.to_string(), - stack, - } - } - - pub fn contains_cycle(&self) -> bool { - self.stack.iter().any(|item| item.starts_with(&self.top)) - } - - pub fn iter(&self) -> impl Iterator { - std::iter::once(&self.top).chain(self.stack.iter().rev()) - } -} - -impl Default for JsonPathStack { - fn default() -> Self { - Self::new() - } -} +pub use path::JsonPathStack; diff --git a/src/operations.rs b/src/operations.rs index 700cd44..3874cb6 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::{collections::BTreeMap, sync::LazyLock}; @@ -7,8 +7,11 @@ use openapiv3::{OpenAPI, Operation, Parameter, ParameterData, ReferenceOr}; use regex::Regex; use serde::Deserialize; +use serde_json::Value; + use crate::{ context::{Context, Contextual}, + path::EndpointPath, resolve::ReferenceOrResolver, }; @@ -38,24 +41,27 @@ pub struct OperationInfo<'a> { pub shared_parameters: Contextual<'a, Vec>>, } -pub fn operations<'a>( - context: &Context<'a>, -) -> anyhow::Result)>> { - let api = OpenAPI::deserialize(context.raw_openapi).unwrap(); +pub fn operations(raw_openapi: &Value) -> anyhow::Result)>> { + let api = OpenAPI::deserialize(raw_openapi)?; let mut out = Vec::new(); - let context = context.append("paths"); - for (path, ref_or_operation) in api.paths.paths.iter() { - let context = context.append(path); - let (path_item, context) = ref_or_operation.resolve(&context)?; + // Build a context at #/paths/ for resolving path item refs. + let path_endpoint = EndpointPath::for_path(path); + let path_context = Context::for_endpoint(raw_openapi, path_endpoint); + let (path_item, path_context) = ref_or_operation.resolve(&path_context)?; - let shared_parameters = - Contextual::new(context.append("parameters"), path_item.parameters.clone()); + let shared_parameters = Contextual::new( + path_context.append("parameters"), + path_item.parameters.clone(), + ); for (method, operation) in path_item.iter() { - let context = context.append(method); + // Build endpoint path for this operation: #/paths// + let endpoint = EndpointPath::for_path(path).append(method); + let context = Context::for_endpoint(raw_openapi, endpoint); + let op_key = OperationKey::new(path, method); let op_info = OperationInfo { path: path.clone(), diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..6df11a5 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,620 @@ +// Copyright 2026 Oxide Computer Company + +//! Strongly-typed JSON path stack for tracking location in OpenAPI documents. +//! +//! This module provides types for tracking location while traversing an OpenAPI +//! document, particularly when following `$ref` references. +//! +//! ## State machine +//! +//! The [`JsonPathStack`] uses an internal state machine ([`PathState`]) that +//! enforces these invariants at the type level: +//! +//! - **`PathsRoot`**: At `#/paths`, used only for reporting operation add/remove. +//! Cannot follow refs or append segments from this state. +//! - **`AtEndpoint`**: At an endpoint path (`#/paths//...`), no refs +//! followed yet. Can append segments or push a ref. +//! - **`AtComponent`**: At a ref target (any JSON pointer), with a reference +//! chain. The chain always has exactly one endpoint origin (the first ref), +//! followed by zero or more intermediates. +//! +//! This makes illegal states unrepresentable: you cannot have endpoint refs +//! mixed into the intermediate chain, or push refs from `PathsRoot`. + +use std::fmt; + +/// Error returned when `JsonPathStack::push()` receives an invalid reference. +/// +/// A valid reference must be a JSON pointer starting with `#/`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct InvalidComponentRef { + /// The invalid reference that was provided. + pub(crate) reference: String, +} + +impl fmt::Display for InvalidComponentRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "invalid reference {:?}: expected JSON pointer starting with #/", + self.reference + ) + } +} + +impl std::error::Error for InvalidComponentRef {} + +/// An endpoint path, guaranteed to start with `#/paths/`. +/// +/// This represents a location within the paths section of an OpenAPI document. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct EndpointPath(String); + +impl EndpointPath { + /// Create an endpoint path from a raw API path (like `/users/{id}`). + /// + /// This escapes the path according to JSON pointer rules (RFC 6901). + pub(crate) fn for_path(api_path: &str) -> Self { + let escaped = escape_json_pointer_segment(api_path); + Self(format!("#/paths/{}", escaped)) + } + + /// Append a path segment, escaping special characters per RFC 6901. + pub(crate) fn append(&self, segment: &str) -> Self { + let escaped = escape_json_pointer_segment(segment); + Self(format!("{}/{}", self.0, escaped)) + } + + /// Get the JSON pointer string. + fn as_str(&self) -> &str { + &self.0 + } +} + +/// A JSON pointer reference (starting with `#/`): guaranteed by construction. +/// +/// This represents a location after following at least one `$ref`. It can point +/// to any location in the document, not only components. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct RefTargetPath(String); + +impl RefTargetPath { + /// Parse from a JSON pointer. Returns `None` if not a valid local ref. + fn parse(pointer: &str) -> Option { + pointer.starts_with("#/").then(|| Self(pointer.to_string())) + } + + /// Append a path segment, escaping special characters per RFC 6901. + fn append(&self, segment: &str) -> Self { + let escaped = escape_json_pointer_segment(segment); + Self(format!("{}/{}", self.0, escaped)) + } + + /// Get the JSON pointer string. + fn as_str(&self) -> &str { + &self.0 + } +} + +/// The state of the path stack, encoding the invariants: +/// +/// - If refs have been followed, the first was always from an endpoint. +/// - All subsequent refs form a chain of intermediate locations. +/// - Transitions: endpoint → ref target, or ref target → ref target. +#[derive(Clone, Debug, PartialEq, Eq)] +enum PathState { + /// At the paths root (`#/paths`), used only for operation add/remove reporting. + /// Cannot follow refs from this state. + PathsRoot, + + /// At an endpoint, no refs followed yet. + AtEndpoint(EndpointPath), + + /// At a component, with reference chain. + AtComponent { + /// The current component location. + current: RefTargetPath, + /// Where the first ref was followed from (always an endpoint, with + /// `/$ref` appended). + origin_ref: EndpointPath, + /// Intermediate refs (all from components, each with `/$ref` appended). + intermediate_refs: Vec, + }, +} + +/// Strongly-typed path stack for tracking location in OpenAPI documents. +/// +/// The stack tracks the location while traversing an OpenAPI document, +/// particularly when following `$ref` references. +#[derive(Clone)] +pub struct JsonPathStack { + state: PathState, +} + +impl JsonPathStack { + /// Create a path stack starting at the given endpoint. + /// + /// The endpoint must be a valid endpoint path (starting with `#/paths/`). + pub(crate) fn for_endpoint(endpoint: EndpointPath) -> Self { + Self { + state: PathState::AtEndpoint(endpoint), + } + } + + /// Create a path stack at `#/paths` (no specific endpoint). + /// + /// This is used only when reporting that an operation was added or removed, + /// where one side doesn't have the operation at all. In normal use, prefer + /// `for_endpoint()`. + pub(crate) fn paths_root() -> Self { + Self { + state: PathState::PathsRoot, + } + } + + /// Return `true` if this stack has a root endpoint (not created via + /// `paths_root()`). + #[cfg(test)] + fn has_root(&self) -> bool { + !matches!(self.state, PathState::PathsRoot) + } + + /// Return `true` if the current location is an endpoint (not a schema). + #[cfg(test)] + fn is_at_endpoint(&self) -> bool { + matches!(self.state, PathState::AtEndpoint(_)) + } + + /// Return `true` if the current location is a schema. + #[cfg(test)] + fn is_at_component(&self) -> bool { + matches!(self.state, PathState::AtComponent { .. }) + } + + /// Get the JSON pointer string for the current location. + pub fn current_pointer(&self) -> &str { + match &self.state { + PathState::PathsRoot => "#/paths", + PathState::AtEndpoint(path) => path.as_str(), + PathState::AtComponent { current, .. } => current.as_str(), + } + } + + /// Append a path segment to the current location. + /// + /// This does not push a new reference; it extends the current path. + /// + /// # Panics + /// + /// Panics if called on a `paths_root()` stack (use `for_endpoint()` for traversal). + pub(crate) fn append(&self, segment: &str) -> JsonPathStack { + let state = match &self.state { + PathState::PathsRoot => { + panic!("cannot append to paths_root (use for_endpoint for traversal)") + } + PathState::AtEndpoint(path) => PathState::AtEndpoint(path.append(segment)), + PathState::AtComponent { + current, + origin_ref, + intermediate_refs, + } => PathState::AtComponent { + current: current.append(segment), + origin_ref: origin_ref.clone(), + intermediate_refs: intermediate_refs.clone(), + }, + }; + Self { state } + } + + /// Push a schema reference onto the stack. + /// + /// This records the current location (with `/$ref` appended) in the + /// reference chain and sets the new current location to the schema path. + /// + /// Returns an error if `reference` is not a valid local schema path + /// (`#/...`). + /// + /// # Panics + /// + /// Panics if called on a `paths_root()` stack (a programming error). + pub(crate) fn push(&self, reference: &str) -> Result { + let schema = RefTargetPath::parse(reference).ok_or_else(|| InvalidComponentRef { + reference: reference.to_string(), + })?; + + let state = match &self.state { + PathState::PathsRoot => { + panic!("cannot push from paths_root (no endpoint context)") + } + PathState::AtEndpoint(path) => PathState::AtComponent { + current: schema, + origin_ref: path.append("$ref"), + intermediate_refs: Vec::new(), + }, + PathState::AtComponent { + current, + origin_ref, + intermediate_refs, + } => { + let mut new_intermediates = intermediate_refs.clone(); + new_intermediates.push(current.append("$ref")); + PathState::AtComponent { + current: schema, + origin_ref: origin_ref.clone(), + intermediate_refs: new_intermediates, + } + } + }; + Ok(Self { state }) + } + + /// Check if the stack contains a cycle. + /// + /// A cycle is detected when the current location is a path-segment-aligned + /// prefix of any entry in the reference chain. This means the current + /// schema (or an ancestor of it) was already visited, so descending into + /// it again would loop forever. + pub fn contains_cycle(&self) -> bool { + match &self.state { + PathState::PathsRoot | PathState::AtEndpoint(_) => false, + PathState::AtComponent { + current, + origin_ref, + intermediate_refs, + } => { + // In OAS 3.0, paths can `$ref` other paths, so we can't + // only look within intermediate_refs to check for cycles: + // there's a chance of a cycle involving the origin as well. + let current_str = current.as_str(); + is_path_ancestor_of(current_str, origin_ref.as_str()) + || intermediate_refs + .iter() + .any(|r| is_path_ancestor_of(current_str, r.as_str())) + } + } + } + + /// Iterate over the path reference stack from top (current) to bottom + /// (origin). + /// + /// This yields the current path first, then the reference chain in reverse + /// order (most recent reference first, origin endpoint last). + pub fn iter(&self) -> impl Iterator { + match &self.state { + PathState::PathsRoot => { + Box::new(std::iter::once("#/paths")) as Box> + } + PathState::AtEndpoint(path) => { + Box::new(std::iter::once(path.as_str())) as Box> + } + PathState::AtComponent { + current, + origin_ref, + intermediate_refs, + } => Box::new( + std::iter::once(current.as_str()) + .chain(intermediate_refs.iter().rev().map(RefTargetPath::as_str)) + .chain(std::iter::once(origin_ref.as_str())), + ), + } + } +} + +impl fmt::Debug for JsonPathStack { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Format as a list of strings for compatibility with existing tests. + let mut out = f.debug_list(); + for path in self.iter() { + out.entry(&path); + } + out.finish() + } +} + +impl fmt::Display for JsonPathStack { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut first = true; + for path in self.iter() { + if !first { + write!(f, " -> ")?; + } + write!(f, "{}", path)?; + first = false; + } + Ok(()) + } +} + +/// Escape a segment for use in a JSON pointer per RFC 6901. +fn escape_json_pointer_segment(segment: &str) -> String { + segment.replace('~', "~0").replace('/', "~1") +} + +/// Check if `ancestor` is a path-segment-aligned prefix of `path`. +/// +/// Returns `true` if `path` starts with `ancestor` and the character +/// immediately following the prefix (if any) is `/`. This prevents false +/// matches where schema names share a common string prefix (e.g., `User` +/// matching `UserProfile`). +fn is_path_ancestor_of(ancestor: &str, path: &str) -> bool { + path.starts_with(ancestor) + && path + .as_bytes() + .get(ancestor.len()) + .is_none_or(|&b| b == b'/') +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn endpoint_path_for_path() { + let path = EndpointPath::for_path("/users"); + assert_eq!(path.as_str(), "#/paths/~1users"); + } + + #[test] + fn endpoint_path_for_path_escapes() { + // Paths with special characters get escaped. + let path = EndpointPath::for_path("/users/{id}/posts"); + assert_eq!(path.as_str(), "#/paths/~1users~1{id}~1posts"); + } + + #[test] + fn endpoint_path_append() { + let path = EndpointPath::for_path("/users") + .append("get") + .append("responses"); + assert_eq!(path.as_str(), "#/paths/~1users/get/responses"); + } + + #[test] + fn endpoint_path_append_escapes() { + let path = EndpointPath::for_path("/users") + .append("foo/bar") + .append("a~b"); + assert_eq!(path.as_str(), "#/paths/~1users/foo~1bar/a~0b"); + } + + #[test] + fn component_path_parse_valid() { + let path = RefTargetPath::parse("#/components/schemas/User"); + assert!(path.is_some()); + assert_eq!(path.unwrap().as_str(), "#/components/schemas/User"); + } + + #[test] + fn component_path_parse_valid_various() { + // Any JSON pointer is valid. + assert!(RefTargetPath::parse("#/components/responses/NotFound").is_some()); + assert!(RefTargetPath::parse("#/components/parameters/PageSize").is_some()); + assert!(RefTargetPath::parse("#/paths/~1users/get").is_some()); + assert!(RefTargetPath::parse("#/definitions/User").is_some()); + } + + #[test] + fn component_path_parse_invalid() { + // Must start with #/. + assert!(RefTargetPath::parse("components/schemas/User").is_none()); + assert!(RefTargetPath::parse("/components/schemas/User").is_none()); + assert!(RefTargetPath::parse("https://example.com/schema.json").is_none()); + } + + #[test] + fn component_path_append() { + let path = RefTargetPath::parse("#/components/schemas/User") + .unwrap() + .append("properties") + .append("name"); + assert_eq!(path.as_str(), "#/components/schemas/User/properties/name"); + } + + #[test] + fn json_path_stack_for_endpoint() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint); + + assert_eq!(stack.current_pointer(), "#/paths/~1users/get"); + assert!(stack.is_at_endpoint()); + assert!(stack.has_root()); + } + + #[test] + fn json_path_stack_paths_root() { + let stack = JsonPathStack::paths_root(); + + assert_eq!(stack.current_pointer(), "#/paths"); + assert!(!stack.has_root()); + } + + #[test] + fn json_path_stack_append() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint) + .append("responses") + .append("200") + .append("schema"); + + assert_eq!( + stack.current_pointer(), + "#/paths/~1users/get/responses/200/schema" + ); + assert!(stack.is_at_endpoint()); + } + + #[test] + fn json_path_stack_push() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint) + .append("responses") + .append("200") + .append("schema") + .push("#/components/schemas/User") + .unwrap(); + + assert_eq!(stack.current_pointer(), "#/components/schemas/User"); + assert!(stack.is_at_component()); + + let entries: Vec<_> = stack.iter().collect(); + assert_eq!(entries.len(), 2); + assert_eq!(entries[0], "#/components/schemas/User"); + assert_eq!(entries[1], "#/paths/~1users/get/responses/200/schema/$ref"); + } + + #[test] + fn json_path_stack_cycle_detection() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint) + .push("#/components/schemas/User") + .unwrap() + .append("properties") + .append("manager"); + + // No cycle yet. + assert!(!stack.contains_cycle()); + + // Push a reference back to User, creating a cycle. + let stack = stack.push("#/components/schemas/User").unwrap(); + assert!(stack.contains_cycle()); + } + + // Schemas whose names share a common string prefix must not be treated as + // cycles. e.g. "User" is a prefix of "UserProfile" at the string level, but + // they are different schemas. + #[test] + fn cycle_detection_no_false_positive_on_shared_prefix() { + let endpoint = EndpointPath::for_path("/users").append("get"); + + let stack = JsonPathStack::for_endpoint(endpoint) + .push("#/components/schemas/User") + .unwrap() + .append("properties") + .append("manager") + .push("#/components/schemas/UserProfile") + .unwrap(); + + assert!(!stack.contains_cycle()); + } + + #[test] + fn cycle_detection_no_false_positive_on_shared_prefix_origin() { + // The origin endpoint path could also be a string prefix of + // the current location without being an ancestor. + let endpoint = EndpointPath::for_path("/users").append("get"); + + // Follow a ref to a schema whose path happens to start with + // the same characters as the origin but at a different location. + let stack = JsonPathStack::for_endpoint(endpoint) + .push("#/paths/~1users/get-details") + .unwrap(); + + // "AB" is not "A": no cycle, even though "A" is a string prefix + // of "AB". + assert!(!stack.contains_cycle()); + } + + #[test] + fn cycle_detection_true_positive_through_subpath() { + // A cycle exists when the current schema is an ancestor of a + // previously visited location (we'd descend into the same subtree). + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint) + .push("#/components/schemas/User") + .unwrap() + .append("properties") + .append("address") + .push("#/components/schemas/Address") + .unwrap() + .append("properties") + .append("owner") + // Cycle back to User: User -> ... -> Address -> ... -> User. + .push("#/components/schemas/User") + .unwrap(); + + assert!(stack.contains_cycle()); + } + + #[test] + fn is_path_ancestor_of_basics() { + // Exact match (after stripping /$ref the paths are equal). + assert!(is_path_ancestor_of( + "#/components/schemas/User", + "#/components/schemas/User/$ref" + )); + + // True ancestor: current is a parent of the chain entry. + assert!(is_path_ancestor_of( + "#/components/schemas/User", + "#/components/schemas/User/properties/name/$ref" + )); + + // False: shared string prefix but different schema name. + assert!(!is_path_ancestor_of( + "#/components/schemas/User", + "#/components/schemas/UserProfile/$ref" + )); + + // False: completely unrelated paths. + assert!(!is_path_ancestor_of( + "#/components/schemas/User", + "#/components/schemas/Address/$ref" + )); + + // Edge: ancestor equals path exactly (no trailing content). + assert!(is_path_ancestor_of( + "#/components/schemas/User", + "#/components/schemas/User" + )); + } + + #[test] + fn json_path_stack_iter_order() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint) + .push("#/components/schemas/A") + .unwrap() + .push("#/components/schemas/B") + .unwrap(); + + let entries: Vec<_> = stack.iter().collect(); + assert_eq!(entries.len(), 3); + // Current is first. + assert_eq!(entries[0], "#/components/schemas/B"); + // Then refs in reverse order (most recent first). + assert_eq!(entries[1], "#/components/schemas/A/$ref"); + // Origin is last. + assert_eq!(entries[2], "#/paths/~1users/get/$ref"); + } + + #[test] + fn json_path_stack_display() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint) + .push("#/components/schemas/User") + .unwrap(); + + let displayed = format!("{}", stack); + assert_eq!( + displayed, + "#/components/schemas/User -> #/paths/~1users/get/$ref" + ); + } + + #[test] + fn json_path_stack_push_invalid_ref_returns_error() { + let endpoint = EndpointPath::for_path("/users").append("get"); + let stack = JsonPathStack::for_endpoint(endpoint); + + let err = stack + .push("not/a/json/pointer") + .expect_err("expected error for invalid reference"); + + assert_eq!(err.reference, "not/a/json/pointer"); + assert_eq!( + err.to_string(), + "invalid reference \"not/a/json/pointer\": \ + expected JSON pointer starting with #/" + ); + } +} diff --git a/src/resolve.rs b/src/resolve.rs index e828842..187191e 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::borrow::Cow; @@ -33,8 +33,7 @@ where }; loop { - assert!(target.starts_with("#/")); - context = context.push(target.as_ref()); + context = context.push(target.as_ref())?; let subtree = context .raw_openapi diff --git a/src/schema.rs b/src/schema.rs index 133c978..30b5faf 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::fmt; @@ -6,7 +6,7 @@ use openapiv3::{AdditionalProperties, ArrayType, ObjectType, ReferenceOr, Schema use crate::{ ChangeClass, ChangeComparison, ChangeDetails, - compare::Compare, + compare::{Compare, VisitedKey}, context::{Contextual, ToContext}, setops::SetCompare, }; @@ -198,11 +198,12 @@ impl Compare { // Return the cached compatibility of these schemas so that we don't // generate redundant notes. - if let Some(equal) = self.visited.get(&( + let key = VisitedKey::new( comparison, - old_schema.context().stack().top.clone(), - new_schema.context().stack().top.clone(), - )) { + old_schema.context().stack(), + new_schema.context().stack(), + ); + if let Some(equal) = self.visited.get(&key) { return Ok(*equal); } @@ -276,14 +277,7 @@ impl Compare { )?; // Cache the result. - self.visited.insert( - ( - comparison, - old_schema.context().stack().top.clone(), - new_schema.context().stack().top.clone(), - ), - nullable_equal && schema_equal, - ); + self.visited.insert(key, nullable_equal && schema_equal); Ok(nullable_equal && schema_equal) } diff --git a/tests/cases/simple/output/add-cookie-parameter.out b/tests/cases/simple/output/add-cookie-parameter.out index 805b882..589dbfd 100644 --- a/tests/cases/simple/output/add-cookie-parameter.out +++ b/tests/cases/simple/output/add-cookie-parameter.out @@ -1,6 +1,6 @@ --- add-cookie-parameter.json +++ patched -@@ -365,6 +365,16 @@ +@@ "get": { "description": "A simple ping endpoint that does nothing.", "operationId": "ping", @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "A new, optional parameter 'tracking' was added", - old_path: [ - "#/paths/~1ping/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1ping/get", + ], + new: [ + "#/paths/~1ping/get/parameters/0", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1ping/get/parameters/0", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "A new, optional parameter 'tracking' was added", + class: ForwardIncompatible, + details: Added, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-default-response.out b/tests/cases/simple/output/add-default-response.out index 712492a..9396245 100644 --- a/tests/cases/simple/output/add-default-response.out +++ b/tests/cases/simple/output/add-default-response.out @@ -1,6 +1,6 @@ --- add-default-response.json +++ patched -@@ -368,6 +368,16 @@ +@@ "responses": { "200": { "description": "Ping successful" @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "operation added a default response", - old_path: [ - "#/paths/~1ping/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1ping/get", + ], + new: [ + "#/paths/~1ping/get", + ], + comparison: Output, + }, ], - new_path: [ - "#/paths/~1ping/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "operation added a default response", + class: Unhandled, + details: Added, + }, ], - comparison: Output, - class: Unhandled, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-header-parameter.out b/tests/cases/simple/output/add-header-parameter.out index 006a63b..4983e6b 100644 --- a/tests/cases/simple/output/add-header-parameter.out +++ b/tests/cases/simple/output/add-header-parameter.out @@ -1,6 +1,6 @@ --- add-header-parameter.json +++ patched -@@ -365,6 +365,16 @@ +@@ "get": { "description": "A simple ping endpoint that does nothing.", "operationId": "ping", @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "A new, optional parameter 'X-Trace-Id' was added", - old_path: [ - "#/paths/~1ping/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1ping/get", + ], + new: [ + "#/paths/~1ping/get/parameters/0", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1ping/get/parameters/0", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "A new, optional parameter 'X-Trace-Id' was added", + class: ForwardIncompatible, + details: Added, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-operation-with-id.out b/tests/cases/simple/output/add-operation-with-id.out index 7b51342..16de102 100644 --- a/tests/cases/simple/output/add-operation-with-id.out +++ b/tests/cases/simple/output/add-operation-with-id.out @@ -1,6 +1,6 @@ --- add-operation-with-id.json +++ patched -@@ -314,6 +314,17 @@ +@@ "summary": "Update an item" } }, @@ -23,15 +23,25 @@ Result for patch: [ Change { - message: "The operation new_endpoint was added", - old_path: [ - "#/paths", + paths: [ + ChangePath { + old: [ + "#/paths", + ], + new: [ + "#/paths/~1new-endpoint/get", + ], + comparison: Structural, + }, ], - new_path: [ - "#/paths/~1new-endpoint/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The operation new_endpoint was added", + class: ForwardIncompatible, + details: Added, + }, ], - comparison: Structural, - class: ForwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-operation.out b/tests/cases/simple/output/add-operation.out index f938e86..23d6f9b 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 -@@ -238,6 +238,16 @@ +@@ "summary": "Get arrays" } }, @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "The operation was added", - old_path: [ - "#/paths", + paths: [ + ChangePath { + old: [ + "#/paths", + ], + new: [ + "#/paths/~1foo/get", + ], + comparison: Structural, + }, ], - new_path: [ - "#/paths/~1foo/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The operation was added", + class: ForwardIncompatible, + details: Added, + }, ], - comparison: Structural, - class: ForwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-optional-body.out b/tests/cases/simple/output/add-optional-body.out index 75fe676..6d3527d 100644 --- a/tests/cases/simple/output/add-optional-body.out +++ b/tests/cases/simple/output/add-optional-body.out @@ -1,6 +1,6 @@ --- add-optional-body.json +++ patched -@@ -317,6 +317,21 @@ +@@ "/no-body": { "post": { "operationId": "no_body", @@ -27,15 +27,25 @@ Result for patch: [ Change { - message: "no body parameter was specified and now one is accepted", - old_path: [ - "#/paths/~1no-body/post", + paths: [ + ChangePath { + old: [ + "#/paths/~1no-body/post", + ], + new: [ + "#/paths/~1no-body/post", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1no-body/post", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "no body parameter was specified and now one is accepted", + class: ForwardIncompatible, + details: Added, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-optional-parameter.out b/tests/cases/simple/output/add-optional-parameter.out index 34b0217..090280d 100644 --- a/tests/cases/simple/output/add-optional-parameter.out +++ b/tests/cases/simple/output/add-optional-parameter.out @@ -1,6 +1,6 @@ --- add-optional-parameter.json +++ patched -@@ -257,6 +257,15 @@ +@@ "schema": { "type": "string" } @@ -21,15 +21,25 @@ Result for patch: [ Change { - message: "A new, optional parameter 'limit' was added", - old_path: [ - "#/paths/~1hello~1{name}/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get", + ], + new: [ + "#/paths/~1hello~1{name}/get/parameters/2", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get/parameters/2", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "A new, optional parameter 'limit' was added", + class: ForwardIncompatible, + details: Added, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-required-body.out b/tests/cases/simple/output/add-required-body.out index 47d7d4e..4c76e6c 100644 --- a/tests/cases/simple/output/add-required-body.out +++ b/tests/cases/simple/output/add-required-body.out @@ -1,6 +1,6 @@ --- add-required-body.json +++ patched -@@ -317,6 +317,21 @@ +@@ "/no-body": { "post": { "operationId": "no_body", @@ -27,15 +27,25 @@ Result for patch: [ Change { - message: "no body parameter was specified and now one is required", - old_path: [ - "#/paths/~1no-body/post", + paths: [ + ChangePath { + old: [ + "#/paths/~1no-body/post", + ], + new: [ + "#/paths/~1no-body/post", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1no-body/post", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "no body parameter was specified and now one is required", + class: Incompatible, + details: AddedRequired, + }, ], - comparison: Input, - class: Incompatible, - details: AddedRequired, }, ] diff --git a/tests/cases/simple/output/add-required-parameter.out b/tests/cases/simple/output/add-required-parameter.out index b2e82b0..ab0ae99 100644 --- a/tests/cases/simple/output/add-required-parameter.out +++ b/tests/cases/simple/output/add-required-parameter.out @@ -1,6 +1,6 @@ --- add-required-parameter.json +++ patched -@@ -257,6 +257,15 @@ +@@ "schema": { "type": "string" } @@ -21,15 +21,25 @@ Result for patch: [ Change { - message: "A new, required parameter 'format' was added", - old_path: [ - "#/paths/~1hello~1{name}/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get", + ], + new: [ + "#/paths/~1hello~1{name}/get/parameters/2", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get/parameters/2", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "A new, required parameter 'format' was added", + class: BackwardIncompatible, + details: AddedRequired, + }, ], - comparison: Input, - class: BackwardIncompatible, - details: AddedRequired, }, ] diff --git a/tests/cases/simple/output/add-response-code.out b/tests/cases/simple/output/add-response-code.out index 4d98490..0181b93 100644 --- a/tests/cases/simple/output/add-response-code.out +++ b/tests/cases/simple/output/add-response-code.out @@ -1,6 +1,6 @@ --- add-response-code.json +++ patched -@@ -368,6 +368,9 @@ +@@ "responses": { "200": { "description": "Ping successful" @@ -15,15 +15,25 @@ Result for patch: [ Change { - message: "operation added a new response code 500", - old_path: [ - "#/paths/~1ping/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1ping/get", + ], + new: [ + "#/paths/~1ping/get", + ], + comparison: Output, + }, ], - new_path: [ - "#/paths/~1ping/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "operation added a new response code 500", + class: BackwardIncompatible, + details: Added, + }, ], - comparison: Output, - class: BackwardIncompatible, - details: Added, }, ] diff --git a/tests/cases/simple/output/add-type-extension.out b/tests/cases/simple/output/add-type-extension.out index 2b9722c..94545a5 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 -@@ -88,7 +88,10 @@ +@@ "required": [ "message" ], @@ -17,17 +17,27 @@ Result for patch: [ Change { - message: "schema metadata changed", - old_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/allof-to-anyof.out b/tests/cases/simple/output/allof-to-anyof.out index c65c7a9..b81e3bd 100644 --- a/tests/cases/simple/output/allof-to-anyof.out +++ b/tests/cases/simple/output/allof-to-anyof.out @@ -1,6 +1,6 @@ --- allof-to-anyof.json +++ patched -@@ -58,12 +58,12 @@ +@@ } }, "via_allof": { @@ -20,17 +20,27 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 index 2712e0a..ed44066 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,6 +1,6 @@ --- allof-to-oneof-with-type-change.json +++ patched -@@ -58,12 +58,12 @@ +@@ } }, "via_allof": { @@ -16,7 +16,7 @@ }, "via_anyof": { "anyOf": [ -@@ -137,7 +137,7 @@ +@@ "SubType": { "properties": { "value": { @@ -30,33 +30,53 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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", + paths: [ + ChangePath { + old: [ + "#/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: [ + "#/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, + }, ], - 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", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - 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 index 14f9d00..138bba1 100644 --- a/tests/cases/simple/output/allof-to-oneof.out +++ b/tests/cases/simple/output/allof-to-oneof.out @@ -1,6 +1,6 @@ --- allof-to-oneof.json +++ patched -@@ -58,12 +58,12 @@ +@@ } }, "via_allof": { @@ -21,17 +21,27 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 index f4d9b89..b9e4d5d 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,6 +1,6 @@ --- allof-to-ref-with-type-change.json +++ patched -@@ -58,12 +58,7 @@ +@@ } }, "via_allof": { @@ -14,7 +14,7 @@ }, "via_anyof": { "anyOf": [ -@@ -136,6 +131,9 @@ +@@ }, "SubType": { "properties": { @@ -29,33 +29,53 @@ Result for patch: [ Change { - message: "schema metadata removed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata removed", + class: Trivial, + details: Metadata, + }, ], - 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_allof/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/SubType", - "#/components/schemas/GreetingResponse/properties/via_allof/$ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object properties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - 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 index 7f3de13..f678b9f 100644 --- a/tests/cases/simple/output/allof-to-ref.out +++ b/tests/cases/simple/output/allof-to-ref.out @@ -1,6 +1,6 @@ --- allof-to-ref.json +++ patched -@@ -58,12 +58,7 @@ +@@ } }, "via_allof": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - message: "schema metadata removed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_allof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_allof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata removed", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/any-schema-change.out b/tests/cases/simple/output/any-schema-change.out index 35a3e91..6946867 100644 --- a/tests/cases/simple/output/any-schema-change.out +++ b/tests/cases/simple/output/any-schema-change.out @@ -1,6 +1,6 @@ --- any-schema-change.json +++ patched -@@ -48,6 +48,7 @@ +@@ }, "GreetingResponse": { "properties": { @@ -13,17 +13,27 @@ Result for patch: [ Change { - message: "object properties changed", - old_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object properties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/anyof-to-allof.out b/tests/cases/simple/output/anyof-to-allof.out index ced8976..882217b 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 -@@ -66,12 +66,12 @@ +@@ "description": "Via allOf." }, "via_anyof": { @@ -20,17 +20,27 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_anyof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 index 0e1f38d..95136e7 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 -@@ -66,12 +66,12 @@ +@@ "description": "Via allOf." }, "via_anyof": { @@ -21,17 +21,27 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_anyof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 index 2e1d099..d4cc3d9 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 -@@ -66,12 +66,7 @@ +@@ "description": "Via allOf." }, "via_anyof": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - message: "schema metadata removed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_anyof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_anyof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_anyof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata removed", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/array-items-add.out b/tests/cases/simple/output/array-items-add.out index f197b94..ff3f591 100644 --- a/tests/cases/simple/output/array-items-add.out +++ b/tests/cases/simple/output/array-items-add.out @@ -1,6 +1,6 @@ --- array-items-add.json +++ patched -@@ -12,9 +12,6 @@ +@@ ] }, "ArrayWithConstraints": { @@ -15,17 +15,27 @@ Result for patch: [ Change { - message: "array items changed", - old_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "array items changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/array-items-remove.out b/tests/cases/simple/output/array-items-remove.out index 426e982..0d4f31e 100644 --- a/tests/cases/simple/output/array-items-remove.out +++ b/tests/cases/simple/output/array-items-remove.out @@ -1,6 +1,6 @@ --- array-items-remove.json +++ patched -@@ -146,7 +146,7 @@ +@@ "properties": { "children": { "items": { @@ -14,18 +14,28 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/components/schemas/Tree", - "#/components/schemas/Tree/properties/children/items/$ref", - "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/Tree", + "#/components/schemas/Tree/properties/children/items/$ref", + "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/Tree/properties/children/items", + "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/Tree/properties/children/items", - "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Output, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/array-max-items-change.out b/tests/cases/simple/output/array-max-items-change.out index b00e190..1057ee5 100644 --- a/tests/cases/simple/output/array-max-items-change.out +++ b/tests/cases/simple/output/array-max-items-change.out @@ -1,6 +1,6 @@ --- array-max-items-change.json +++ patched -@@ -15,7 +15,7 @@ +@@ "items": { "type": "string" }, @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "array maxItems changed", - old_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "array maxItems changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/array-min-items-change.out b/tests/cases/simple/output/array-min-items-change.out index df42d55..d2e39df 100644 --- a/tests/cases/simple/output/array-min-items-change.out +++ b/tests/cases/simple/output/array-min-items-change.out @@ -1,6 +1,6 @@ --- array-min-items-change.json +++ patched -@@ -16,7 +16,7 @@ +@@ "type": "string" }, "maxItems": 10, @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "array minItems changed", - old_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "array minItems changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/array-unique-items-change.out b/tests/cases/simple/output/array-unique-items-change.out index 34e50e4..3561ec6 100644 --- a/tests/cases/simple/output/array-unique-items-change.out +++ b/tests/cases/simple/output/array-unique-items-change.out @@ -1,6 +1,6 @@ --- array-unique-items-change.json +++ patched -@@ -18,7 +18,7 @@ +@@ "maxItems": 10, "minItems": 1, "type": "array", @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "array uniqueItems changed", - old_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ArrayWithConstraints", + "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ArrayWithConstraints", - "#/paths/~1arrays/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "array uniqueItems changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/body-description-change.out b/tests/cases/simple/output/body-description-change.out index 3e2eb01..70d0cf7 100644 --- a/tests/cases/simple/output/body-description-change.out +++ b/tests/cases/simple/output/body-description-change.out @@ -1,6 +1,6 @@ --- body-description-change.json +++ patched -@@ -285,6 +285,7 @@ +@@ } } }, @@ -13,15 +13,25 @@ Result for patch: [ Change { - message: "the body metadata (description or extensions) changed", - old_path: [ - "#/paths/~1items/post", + paths: [ + ChangePath { + old: [ + "#/paths/~1items/post", + ], + new: [ + "#/paths/~1items/post", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1items/post", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "the body metadata (description or extensions) changed", + class: Trivial, + details: Metadata, + }, ], - comparison: Input, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/body-extension-change.out b/tests/cases/simple/output/body-extension-change.out index 06cbc81..412535e 100644 --- a/tests/cases/simple/output/body-extension-change.out +++ b/tests/cases/simple/output/body-extension-change.out @@ -1,6 +1,6 @@ --- body-extension-change.json +++ patched -@@ -285,7 +285,8 @@ +@@ } } }, @@ -15,15 +15,25 @@ Result for patch: [ Change { - message: "the body metadata (description or extensions) changed", - old_path: [ - "#/paths/~1items/post", + paths: [ + ChangePath { + old: [ + "#/paths/~1items/post", + ], + new: [ + "#/paths/~1items/post", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1items/post", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "the body metadata (description or extensions) changed", + class: Trivial, + details: Metadata, + }, ], - comparison: Input, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/body-optional-to-required.out b/tests/cases/simple/output/body-optional-to-required.out index b09a68f..1140bc4 100644 --- a/tests/cases/simple/output/body-optional-to-required.out +++ b/tests/cases/simple/output/body-optional-to-required.out @@ -1,6 +1,6 @@ --- body-optional-to-required.json +++ patched -@@ -304,7 +304,7 @@ +@@ } } }, @@ -14,15 +14,25 @@ Result for patch: [ Change { - message: "the body parameter was optional and is now required", - old_path: [ - "#/paths/~1items/put", + paths: [ + ChangePath { + old: [ + "#/paths/~1items/put", + ], + new: [ + "#/paths/~1items/put", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1items/put", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "the body parameter was optional and is now required", + class: BackwardIncompatible, + details: MoreStrict, + }, ], - comparison: Input, - class: BackwardIncompatible, - details: MoreStrict, }, ] diff --git a/tests/cases/simple/output/body-required-to-optional.out b/tests/cases/simple/output/body-required-to-optional.out index a41b06d..1216579 100644 --- a/tests/cases/simple/output/body-required-to-optional.out +++ b/tests/cases/simple/output/body-required-to-optional.out @@ -1,6 +1,6 @@ --- body-required-to-optional.json +++ patched -@@ -285,7 +285,7 @@ +@@ } } }, @@ -14,15 +14,25 @@ Result for patch: [ Change { - message: "the body parameter was required and is now optional", - old_path: [ - "#/paths/~1items/post", + paths: [ + ChangePath { + old: [ + "#/paths/~1items/post", + ], + new: [ + "#/paths/~1items/post", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1items/post", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "the body parameter was required and is now optional", + class: ForwardIncompatible, + details: LessStrict, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: LessStrict, }, ] diff --git a/tests/cases/simple/output/boolean-change.out b/tests/cases/simple/output/boolean-change.out index 6fc74f3..9d002e0 100644 --- a/tests/cases/simple/output/boolean-change.out +++ b/tests/cases/simple/output/boolean-change.out @@ -1,6 +1,6 @@ --- boolean-change.json +++ patched -@@ -159,6 +159,7 @@ +@@ "type": "integer" }, "enabled": { @@ -13,17 +13,27 @@ Result for patch: [ Change { - message: "schema metadata changed", - old_path: [ - "#/components/schemas/TypedProperties/properties/enabled", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/TypedProperties/properties/enabled", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/TypedProperties/properties/enabled", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/TypedProperties/properties/enabled", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/boolean-enum-change.out b/tests/cases/simple/output/boolean-enum-change.out index 670c172..37bf0eb 100644 --- a/tests/cases/simple/output/boolean-enum-change.out +++ b/tests/cases/simple/output/boolean-enum-change.out @@ -1,6 +1,6 @@ --- boolean-enum-change.json +++ patched -@@ -159,6 +159,9 @@ +@@ "type": "integer" }, "enabled": { @@ -15,17 +15,27 @@ Result for patch: [ Change { - message: "boolean schema changed", - old_path: [ - "#/components/schemas/TypedProperties/properties/enabled", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/TypedProperties/properties/enabled", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/TypedProperties/properties/enabled", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/TypedProperties/properties/enabled", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "boolean schema changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/change-default-response.out b/tests/cases/simple/output/change-default-response.out index 51a21da..c72f9ca 100644 --- a/tests/cases/simple/output/change-default-response.out +++ b/tests/cases/simple/output/change-default-response.out @@ -1,6 +1,6 @@ --- change-default-response.json +++ patched -@@ -451,7 +451,7 @@ +@@ } } }, diff --git a/tests/cases/simple/output/change-header-parameter.out b/tests/cases/simple/output/change-header-parameter.out index 8b8fec2..b506dce 100644 --- a/tests/cases/simple/output/change-header-parameter.out +++ b/tests/cases/simple/output/change-header-parameter.out @@ -1,6 +1,6 @@ --- change-header-parameter.json +++ patched -@@ -466,7 +466,7 @@ +@@ "name": "X-Request-Id", "required": true, "schema": { @@ -14,15 +14,25 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/paths/~1with-header/get/parameters/0/schema", + paths: [ + ChangePath { + old: [ + "#/paths/~1with-header/get/parameters/0/schema", + ], + new: [ + "#/paths/~1with-header/get/parameters/0/schema", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1with-header/get/parameters/0/schema", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Input, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/change-operation-parameter-requirement.out b/tests/cases/simple/output/change-operation-parameter-requirement.out index dccb0ce..974f59a 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 -@@ -253,7 +253,7 @@ +@@ "description": "Language for the greeting", "in": "query", "name": "language", @@ -14,15 +14,25 @@ Result for patch: [ Change { - message: "The parameter 'language' was optional and is now required", - old_path: [ - "#/paths/~1hello~1{name}/get/parameters/1", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get/parameters/1", + ], + new: [ + "#/paths/~1hello~1{name}/get/parameters/1", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get/parameters/1", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The parameter 'language' was optional and is now required", + class: BackwardIncompatible, + details: MoreStrict, + }, ], - comparison: Input, - class: BackwardIncompatible, - details: MoreStrict, }, ] diff --git a/tests/cases/simple/output/change-operation-parameter-type.out b/tests/cases/simple/output/change-operation-parameter-type.out index 1a21afd..9252a75 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 -@@ -255,7 +255,7 @@ +@@ "name": "language", "required": false, "schema": { @@ -14,15 +14,25 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/paths/~1hello~1{name}/get/parameters/1/schema", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get/parameters/1/schema", + ], + new: [ + "#/paths/~1hello~1{name}/get/parameters/1/schema", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get/parameters/1/schema", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Input, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/change-property-type.out b/tests/cases/simple/output/change-property-type.out index 59b6235..0084054 100644 --- a/tests/cases/simple/output/change-property-type.out +++ b/tests/cases/simple/output/change-property-type.out @@ -1,6 +1,6 @@ --- change-property-type.json +++ patched -@@ -50,7 +50,7 @@ +@@ "properties": { "message": { "description": "The greeting message", @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/message", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/message", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/message", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/message", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Output, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/inline-to-allof.out b/tests/cases/simple/output/inline-to-allof.out index 5173ba2..954ca32 100644 --- a/tests/cases/simple/output/inline-to-allof.out +++ b/tests/cases/simple/output/inline-to-allof.out @@ -1,6 +1,6 @@ --- inline-to-allof.json +++ patched -@@ -246,7 +246,12 @@ +@@ "in": "path", "name": "name", "schema": { @@ -19,15 +19,25 @@ Result for patch: [ Change { - message: "schema metadata added", - old_path: [ - "#/paths/~1hello~1{name}/get/parameters/0/schema", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get/parameters/0/schema", + ], + new: [ + "#/paths/~1hello~1{name}/get/parameters/0/schema", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get/parameters/0/schema", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - comparison: Input, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/integer-format-change.out b/tests/cases/simple/output/integer-format-change.out index f97a7a4..14535bd 100644 --- a/tests/cases/simple/output/integer-format-change.out +++ b/tests/cases/simple/output/integer-format-change.out @@ -1,6 +1,6 @@ --- integer-format-change.json +++ patched -@@ -156,6 +156,7 @@ +@@ "TypedProperties": { "properties": { "count": { @@ -13,17 +13,27 @@ Result for patch: [ Change { - message: "integer schema changed", - old_path: [ - "#/components/schemas/TypedProperties/properties/count", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/TypedProperties/properties/count", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/TypedProperties/properties/count", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/TypedProperties/properties/count", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "integer schema changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/modify-cycle-type.out b/tests/cases/simple/output/modify-cycle-type.out index 4dd795b..6fb2fa4 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 -@@ -145,10 +145,7 @@ +@@ "Tree": { "properties": { "children": { @@ -17,18 +17,28 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/components/schemas/Tree/properties/children", - "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/Tree/properties/children", + "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/Tree", + "#/components/schemas/Tree/properties/children/$ref", + "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/Tree", - "#/components/schemas/Tree/properties/children/$ref", - "#/paths/~1tree/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Output, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/multi-allof-variant-change.out b/tests/cases/simple/output/multi-allof-variant-change.out index b2c82ec..b50e58b 100644 --- a/tests/cases/simple/output/multi-allof-variant-change.out +++ b/tests/cases/simple/output/multi-allof-variant-change.out @@ -1,6 +1,6 @@ --- multi-allof-variant-change.json +++ patched -@@ -95,7 +95,7 @@ +@@ { "properties": { "id": { @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "unhandled, 'allOf' schema", - old_path: [ - "#/components/schemas/MultiAllOf/allOf", - "#/paths/~1allof/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/MultiAllOf/allOf", + "#/paths/~1allof/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/MultiAllOf/allOf", + "#/paths/~1allof/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/MultiAllOf/allOf", - "#/paths/~1allof/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "unhandled, 'allOf' schema", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/multi-anyof-variant-change.out b/tests/cases/simple/output/multi-anyof-variant-change.out index fc70f50..f82c97e 100644 --- a/tests/cases/simple/output/multi-anyof-variant-change.out +++ b/tests/cases/simple/output/multi-anyof-variant-change.out @@ -1,6 +1,6 @@ --- multi-anyof-variant-change.json +++ patched -@@ -4,7 +4,7 @@ +@@ "AnyOfExample": { "anyOf": [ { @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "unhandled, 'anyOf' schema", - old_path: [ - "#/components/schemas/AnyOfExample", - "#/paths/~1anyof/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/AnyOfExample", + "#/paths/~1anyof/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/AnyOfExample", + "#/paths/~1anyof/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/AnyOfExample", - "#/paths/~1anyof/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "unhandled, 'anyOf' schema", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/multi-oneof-count-change.out b/tests/cases/simple/output/multi-oneof-count-change.out index 8d054d6..94ebd88 100644 --- a/tests/cases/simple/output/multi-oneof-count-change.out +++ b/tests/cases/simple/output/multi-oneof-count-change.out @@ -1,6 +1,6 @@ --- multi-oneof-count-change.json +++ patched -@@ -117,6 +117,9 @@ +@@ }, { "type": "integer" @@ -15,17 +15,27 @@ Result for patch: [ Change { - message: "oneOf schema count changed", - old_path: [ - "#/components/schemas/MultiOneOf/oneOf", - "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/MultiOneOf/oneOf", + "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/MultiOneOf/oneOf", + "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/MultiOneOf/oneOf", - "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "oneOf schema count changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/multi-oneof-variant-change.out b/tests/cases/simple/output/multi-oneof-variant-change.out index 9e67399..95ffac8 100644 --- a/tests/cases/simple/output/multi-oneof-variant-change.out +++ b/tests/cases/simple/output/multi-oneof-variant-change.out @@ -1,6 +1,6 @@ --- multi-oneof-variant-change.json +++ patched -@@ -113,7 +113,7 @@ +@@ "MultiOneOf": { "oneOf": [ { @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/components/schemas/MultiOneOf/oneOf/0", - "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/MultiOneOf/oneOf/0", + "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/MultiOneOf/oneOf/0", + "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/MultiOneOf/oneOf/0", - "#/paths/~1oneof/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Output, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/not-inner-to-allof.out b/tests/cases/simple/output/not-inner-to-allof.out index c3c4808..47df17f 100644 --- a/tests/cases/simple/output/not-inner-to-allof.out +++ b/tests/cases/simple/output/not-inner-to-allof.out @@ -1,6 +1,6 @@ --- not-inner-to-allof.json +++ patched -@@ -54,7 +54,12 @@ +@@ }, "not_a_number": { "not": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/not_a_number/not", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/not_a_number/not", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/not_a_number/not", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - 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 index b3ef213..4af3db6 100644 --- a/tests/cases/simple/output/not-to-allof.out +++ b/tests/cases/simple/output/not-to-allof.out @@ -1,6 +1,6 @@ --- not-to-allof.json +++ patched -@@ -53,9 +53,14 @@ +@@ "type": "string" }, "not_a_number": { @@ -23,17 +23,27 @@ Result for patch: [ Change { - 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/not_a_number", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/not_a_number", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/not_a_number", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/number-constraints-change.out b/tests/cases/simple/output/number-constraints-change.out index 241849d..5754482 100644 --- a/tests/cases/simple/output/number-constraints-change.out +++ b/tests/cases/simple/output/number-constraints-change.out @@ -1,6 +1,6 @@ --- number-constraints-change.json +++ patched -@@ -162,6 +162,7 @@ +@@ "type": "boolean" }, "ratio": { @@ -13,17 +13,27 @@ Result for patch: [ Change { - message: "number schema changed", - old_path: [ - "#/components/schemas/TypedProperties/properties/ratio", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/TypedProperties/properties/ratio", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/TypedProperties/properties/ratio", + "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/TypedProperties/properties/ratio", - "#/paths/~1typed/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "number schema changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/object-additional-props-false.out b/tests/cases/simple/output/object-additional-props-false.out index 7eb6590..5535517 100644 --- a/tests/cases/simple/output/object-additional-props-false.out +++ b/tests/cases/simple/output/object-additional-props-false.out @@ -1,6 +1,6 @@ --- object-additional-props-false.json +++ patched -@@ -121,9 +121,7 @@ +@@ ] }, "ObjectWithConstraints": { @@ -16,17 +16,27 @@ Result for patch: [ Change { - message: "object additionalProperties changed", - old_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object additionalProperties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/object-additional-props-schema-change.out b/tests/cases/simple/output/object-additional-props-schema-change.out index e702620..3248286 100644 --- a/tests/cases/simple/output/object-additional-props-schema-change.out +++ b/tests/cases/simple/output/object-additional-props-schema-change.out @@ -1,6 +1,6 @@ --- object-additional-props-schema-change.json +++ patched -@@ -122,7 +122,7 @@ +@@ }, "ObjectWithConstraints": { "additionalProperties": { @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "schema types changed", - old_path: [ - "#/components/schemas/ObjectWithConstraints/additionalProperties", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ObjectWithConstraints/additionalProperties", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ObjectWithConstraints/additionalProperties", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ObjectWithConstraints/additionalProperties", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Output, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/object-additional-props-type-change.out b/tests/cases/simple/output/object-additional-props-type-change.out index dd355d5..1bbc3f2 100644 --- a/tests/cases/simple/output/object-additional-props-type-change.out +++ b/tests/cases/simple/output/object-additional-props-type-change.out @@ -1,6 +1,6 @@ --- object-additional-props-type-change.json +++ patched -@@ -121,9 +121,7 @@ +@@ ] }, "ObjectWithConstraints": { @@ -16,17 +16,27 @@ Result for patch: [ Change { - message: "object additionalProperties changed", - old_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object additionalProperties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/object-max-properties-change.out b/tests/cases/simple/output/object-max-properties-change.out index c519851..53110fc 100644 --- a/tests/cases/simple/output/object-max-properties-change.out +++ b/tests/cases/simple/output/object-max-properties-change.out @@ -1,6 +1,6 @@ --- object-max-properties-change.json +++ patched -@@ -124,7 +124,7 @@ +@@ "additionalProperties": { "type": "string" }, @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "object maxProperties changed", - old_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object maxProperties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/object-min-properties-change.out b/tests/cases/simple/output/object-min-properties-change.out index cc5eca9..067d2c9 100644 --- a/tests/cases/simple/output/object-min-properties-change.out +++ b/tests/cases/simple/output/object-min-properties-change.out @@ -1,6 +1,6 @@ --- object-min-properties-change.json +++ patched -@@ -125,7 +125,7 @@ +@@ "type": "string" }, "maxProperties": 5, @@ -14,17 +14,27 @@ Result for patch: [ Change { - message: "object minProperties changed", - old_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ObjectWithConstraints", + "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ObjectWithConstraints", - "#/paths/~1objects/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object minProperties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/oneof-to-allof.out b/tests/cases/simple/output/oneof-to-allof.out index 78d37b8..99afdd7 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 -@@ -74,12 +74,12 @@ +@@ "description": "Via anyOf." }, "via_oneof": { @@ -21,17 +21,27 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_oneof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 index c643d8b..c042ab0 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 -@@ -74,12 +74,12 @@ +@@ "description": "Via anyOf." }, "via_oneof": { @@ -21,17 +21,27 @@ 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_oneof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 index 0ec92a5..5ad1463 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 -@@ -74,12 +74,7 @@ +@@ "description": "Via anyOf." }, "via_oneof": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - message: "schema metadata removed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_oneof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_oneof", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_oneof", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata removed", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/param-required-to-optional.out b/tests/cases/simple/output/param-required-to-optional.out index c955a3c..1419a39 100644 --- a/tests/cases/simple/output/param-required-to-optional.out +++ b/tests/cases/simple/output/param-required-to-optional.out @@ -1,6 +1,6 @@ --- param-required-to-optional.json +++ patched -@@ -464,7 +464,7 @@ +@@ { "in": "header", "name": "X-Request-Id", @@ -14,15 +14,25 @@ Result for patch: [ Change { - message: "The parameter 'X-Request-Id' was required and is now optional", - old_path: [ - "#/paths/~1with-header/get/parameters/0", + paths: [ + ChangePath { + old: [ + "#/paths/~1with-header/get/parameters/0", + ], + new: [ + "#/paths/~1with-header/get/parameters/0", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1with-header/get/parameters/0", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The parameter 'X-Request-Id' was required and is now optional", + class: ForwardIncompatible, + details: LessStrict, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: LessStrict, }, ] diff --git a/tests/cases/simple/output/param-schema-to-content.out b/tests/cases/simple/output/param-schema-to-content.out index b9f44c6..2ff8551 100644 --- a/tests/cases/simple/output/param-schema-to-content.out +++ b/tests/cases/simple/output/param-schema-to-content.out @@ -1,6 +1,6 @@ --- param-schema-to-content.json +++ patched -@@ -250,13 +250,17 @@ +@@ } }, { @@ -27,15 +27,25 @@ Result for patch: [ Change { - message: "Unhandled change to parameter schema or content", - old_path: [ - "#/paths/~1hello~1{name}/get/parameters/1", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get/parameters/1", + ], + new: [ + "#/paths/~1hello~1{name}/get/parameters/1", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get/parameters/1", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "Unhandled change to parameter schema or content", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Input, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/ref-chain-change.out b/tests/cases/simple/output/ref-chain-change.out index 9d3bca3..939c83e 100644 --- a/tests/cases/simple/output/ref-chain-change.out +++ b/tests/cases/simple/output/ref-chain-change.out @@ -1,6 +1,6 @@ --- ref-chain-change.json +++ patched -@@ -132,7 +132,7 @@ +@@ "$ref": "#/components/schemas/RefChainB" }, "RefChainB": { @@ -14,39 +14,59 @@ Result for patch: [ Change { - message: "object required properties changed", - old_path: [ - "#/components/schemas/SubType", - "#/components/schemas/RefChainB/$ref", - "#/components/schemas/RefChainA/$ref", - "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/SubType", + "#/components/schemas/RefChainB/$ref", + "#/components/schemas/RefChainA/$ref", + "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ErrorResponse", + "#/components/schemas/RefChainB/$ref", + "#/components/schemas/RefChainA/$ref", + "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ErrorResponse", - "#/components/schemas/RefChainB/$ref", - "#/components/schemas/RefChainA/$ref", - "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object required properties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, Change { - message: "object properties changed", - old_path: [ - "#/components/schemas/SubType", - "#/components/schemas/RefChainB/$ref", - "#/components/schemas/RefChainA/$ref", - "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/SubType", + "#/components/schemas/RefChainB/$ref", + "#/components/schemas/RefChainA/$ref", + "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/ErrorResponse", + "#/components/schemas/RefChainB/$ref", + "#/components/schemas/RefChainA/$ref", + "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/ErrorResponse", - "#/components/schemas/RefChainB/$ref", - "#/components/schemas/RefChainA/$ref", - "#/paths/~1refs/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object properties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/ref-to-allof.out b/tests/cases/simple/output/ref-to-allof.out index 0ffecab..3316bd2 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 -@@ -82,7 +82,12 @@ +@@ ] }, "via_ref": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - message: "schema metadata added", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - 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 index f764670..3349ae3 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 -@@ -82,7 +82,12 @@ +@@ ] }, "via_ref": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - message: "schema metadata added", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/ref-to-inline-allof.out b/tests/cases/simple/output/ref-to-inline-allof.out index 41f89a9..311ff4e 100644 --- a/tests/cases/simple/output/ref-to-inline-allof.out +++ b/tests/cases/simple/output/ref-to-inline-allof.out @@ -1,6 +1,6 @@ --- ref-to-inline-allof.json +++ patched -@@ -82,7 +82,18 @@ +@@ ] }, "via_ref": { @@ -25,32 +25,52 @@ Result for patch: [ Change { - message: "schema metadata added", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_ref/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_ref/0", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref/0", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - 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 4f95563..7224c41 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 -@@ -82,7 +82,12 @@ +@@ ] }, "via_ref": { @@ -19,17 +19,27 @@ Result for patch: [ Change { - message: "schema metadata added", - old_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/via_ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/via_ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata added", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/remove-default-response.out b/tests/cases/simple/output/remove-default-response.out index 615e057..98047b6 100644 --- a/tests/cases/simple/output/remove-default-response.out +++ b/tests/cases/simple/output/remove-default-response.out @@ -1,6 +1,6 @@ --- remove-default-response.json +++ patched -@@ -442,16 +442,6 @@ +@@ "responses": { "200": { "description": "Success" @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "operation removed a default response", - old_path: [ - "#/paths/~1with-default/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1with-default/get", + ], + new: [ + "#/paths/~1with-default/get", + ], + comparison: Output, + }, ], - new_path: [ - "#/paths/~1with-default/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "operation removed a default response", + class: Unhandled, + details: Removed, + }, ], - comparison: Output, - class: Unhandled, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-header-parameter.out b/tests/cases/simple/output/remove-header-parameter.out index 3f5b122..ce18379 100644 --- a/tests/cases/simple/output/remove-header-parameter.out +++ b/tests/cases/simple/output/remove-header-parameter.out @@ -1,6 +1,6 @@ --- remove-header-parameter.json +++ patched -@@ -462,14 +462,6 @@ +@@ "operationId": "with_header", "parameters": [ { @@ -20,15 +20,25 @@ Result for patch: [ Change { - message: "The parameter 'X-Request-Id' was removed", - old_path: [ - "#/paths/~1with-header/get/parameters/0", + paths: [ + ChangePath { + old: [ + "#/paths/~1with-header/get/parameters/0", + ], + new: [ + "#/paths/~1with-header/get", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1with-header/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The parameter 'X-Request-Id' was removed", + class: BackwardIncompatible, + details: Removed, + }, ], - comparison: Input, - class: BackwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-operation-parameter.out b/tests/cases/simple/output/remove-operation-parameter.out index 7fc3327..b539ab5 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 -@@ -248,15 +248,6 @@ +@@ "schema": { "type": "string" } @@ -21,15 +21,25 @@ Result for patch: [ Change { - message: "The parameter 'language' was removed", - old_path: [ - "#/paths/~1hello~1{name}/get/parameters/1", + paths: [ + ChangePath { + old: [ + "#/paths/~1hello~1{name}/get/parameters/1", + ], + new: [ + "#/paths/~1hello~1{name}/get", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1hello~1{name}/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The parameter 'language' was removed", + class: BackwardIncompatible, + details: Removed, + }, ], - comparison: Input, - class: BackwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-operation.out b/tests/cases/simple/output/remove-operation.out index 04ed3a9..e2cf632 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 -@@ -361,18 +361,7 @@ +@@ "summary": "Get oneOf schema" } }, @@ -25,15 +25,25 @@ Result for patch: [ Change { - message: "The operation ping was removed", - old_path: [ - "#/paths/~1ping/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1ping/get", + ], + new: [ + "#/paths", + ], + comparison: Structural, + }, ], - new_path: [ - "#/paths", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The operation ping was removed", + class: BackwardIncompatible, + details: Removed, + }, ], - comparison: Structural, - class: BackwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-optional-body.out b/tests/cases/simple/output/remove-optional-body.out index 4a7a983..4f88a2a 100644 --- a/tests/cases/simple/output/remove-optional-body.out +++ b/tests/cases/simple/output/remove-optional-body.out @@ -1,6 +1,6 @@ --- remove-optional-body.json +++ patched -@@ -296,16 +296,6 @@ +@@ }, "put": { "operationId": "update_item", @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "an optional body parameter was removed", - old_path: [ - "#/paths/~1items/put", + paths: [ + ChangePath { + old: [ + "#/paths/~1items/put", + ], + new: [ + "#/paths/~1items/put", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1items/put", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "an optional body parameter was removed", + class: ForwardIncompatible, + details: Removed, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-required-body.out b/tests/cases/simple/output/remove-required-body.out index 8cb5edc..e7919bb 100644 --- a/tests/cases/simple/output/remove-required-body.out +++ b/tests/cases/simple/output/remove-required-body.out @@ -1,6 +1,6 @@ --- remove-required-body.json +++ patched -@@ -277,16 +277,6 @@ +@@ "/items": { "post": { "operationId": "create_item", @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "a required body parameter was removed", - old_path: [ - "#/paths/~1items/post", + paths: [ + ChangePath { + old: [ + "#/paths/~1items/post", + ], + new: [ + "#/paths/~1items/post", + ], + comparison: Input, + }, ], - new_path: [ - "#/paths/~1items/post", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "a required body parameter was removed", + class: ForwardIncompatible, + details: Removed, + }, ], - comparison: Input, - class: ForwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-response-code.out b/tests/cases/simple/output/remove-response-code.out index 6decf76..ea17cf9 100644 --- a/tests/cases/simple/output/remove-response-code.out +++ b/tests/cases/simple/output/remove-response-code.out @@ -1,6 +1,6 @@ --- remove-response-code.json +++ patched -@@ -440,9 +440,6 @@ +@@ "get": { "operationId": "with_default", "responses": { @@ -15,15 +15,25 @@ Result for patch: [ Change { - message: "operation no longer responds with status 200", - old_path: [ - "#/paths/~1with-default/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1with-default/get", + ], + new: [ + "#/paths/~1with-default/get", + ], + comparison: Output, + }, ], - new_path: [ - "#/paths/~1with-default/get", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "operation no longer responds with status 200", + class: ForwardIncompatible, + details: Removed, + }, ], - comparison: Output, - class: ForwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/remove-unnamed-operation.out b/tests/cases/simple/output/remove-unnamed-operation.out index a895a32..61326da 100644 --- a/tests/cases/simple/output/remove-unnamed-operation.out +++ b/tests/cases/simple/output/remove-unnamed-operation.out @@ -1,6 +1,6 @@ --- remove-unnamed-operation.json +++ patched -@@ -426,16 +426,6 @@ +@@ "summary": "Get typed properties" } }, @@ -22,15 +22,25 @@ Result for patch: [ Change { - message: "The operation was removed", - old_path: [ - "#/paths/~1unnamed/get", + paths: [ + ChangePath { + old: [ + "#/paths/~1unnamed/get", + ], + new: [ + "#/paths", + ], + comparison: Structural, + }, ], - new_path: [ - "#/paths", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "The operation was removed", + class: BackwardIncompatible, + details: Removed, + }, ], - comparison: Structural, - class: BackwardIncompatible, - details: Removed, }, ] diff --git a/tests/cases/simple/output/schema-kind-type-to-oneof.out b/tests/cases/simple/output/schema-kind-type-to-oneof.out index f6591ab..154c588 100644 --- a/tests/cases/simple/output/schema-kind-type-to-oneof.out +++ b/tests/cases/simple/output/schema-kind-type-to-oneof.out @@ -1,6 +1,6 @@ --- schema-kind-type-to-oneof.json +++ patched -@@ -135,12 +135,14 @@ +@@ "$ref": "#/components/schemas/SubType" }, "SubType": { @@ -24,19 +24,29 @@ Result for patch: [ Change { - message: "schema kind changed from regular type to oneOf", - 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", + paths: [ + ChangePath { + old: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/SubType", + "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/SubType", - "#/components/schemas/GreetingResponse/properties/via_allof/0/$ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema kind changed from regular type to oneOf", + class: Incompatible, + details: Datatype, + }, ], - comparison: Output, - class: Incompatible, - details: Datatype, }, ] diff --git a/tests/cases/simple/output/string-format-change.out b/tests/cases/simple/output/string-format-change.out index 1f8edf0..ed74d51 100644 --- a/tests/cases/simple/output/string-format-change.out +++ b/tests/cases/simple/output/string-format-change.out @@ -1,6 +1,6 @@ --- string-format-change.json +++ patched -@@ -50,6 +50,7 @@ +@@ "properties": { "message": { "description": "The greeting message", @@ -13,17 +13,27 @@ Result for patch: [ Change { - message: "string schema changed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/message", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/message", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse/properties/message", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse/properties/message", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "string schema changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] diff --git a/tests/cases/simple/output/type-indirection.out b/tests/cases/simple/output/type-indirection.out index 910cf39..7aabfb2 100644 --- a/tests/cases/simple/output/type-indirection.out +++ b/tests/cases/simple/output/type-indirection.out @@ -1,6 +1,6 @@ --- type-indirection.json +++ patched -@@ -49,8 +49,7 @@ +@@ "GreetingResponse": { "properties": { "message": { @@ -10,7 +10,7 @@ }, "not_a_number": { "not": { -@@ -90,6 +89,13 @@ +@@ ], "type": "object" }, @@ -29,18 +29,28 @@ Result for patch: [ Change { - message: "schema metadata changed", - old_path: [ - "#/components/schemas/GreetingResponse/properties/message", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse/properties/message", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponseMessage", + "#/components/schemas/GreetingResponse/properties/message/$ref", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponseMessage", - "#/components/schemas/GreetingResponse/properties/message/$ref", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema metadata changed", + class: Trivial, + details: Metadata, + }, ], - comparison: Output, - class: Trivial, - details: Metadata, }, ] diff --git a/tests/cases/simple/output/type-rename.out b/tests/cases/simple/output/type-rename.out index bac28a3..b767464 100644 --- a/tests/cases/simple/output/type-rename.out +++ b/tests/cases/simple/output/type-rename.out @@ -1,6 +1,6 @@ --- type-rename.json +++ patched -@@ -46,7 +46,7 @@ +@@ ], "type": "object" }, @@ -9,7 +9,7 @@ "properties": { "message": { "description": "The greeting message", -@@ -264,7 +264,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 37193a0..c45ca6f 100644 --- a/tests/cases/simple/output/unhandled-add-prop.out +++ b/tests/cases/simple/output/unhandled-add-prop.out @@ -1,6 +1,6 @@ --- unhandled-add-prop.json +++ patched -@@ -57,6 +57,10 @@ +@@ "type": "number" } }, @@ -11,7 +11,7 @@ "via_allof": { "allOf": [ { -@@ -86,7 +90,8 @@ +@@ } }, "required": [ @@ -26,31 +26,51 @@ Result for patch: [ Change { - message: "object required properties changed", - old_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object required properties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, Change { - message: "object properties changed", - old_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + paths: [ + ChangePath { + old: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + new: [ + "#/components/schemas/GreetingResponse", + "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + ], + comparison: Output, + }, ], - new_path: [ - "#/components/schemas/GreetingResponse", - "#/paths/~1hello~1{name}/get/responses/200/content/application~1json/schema/$ref", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "object properties changed", + class: Unhandled, + details: UnknownDifference, + }, ], - comparison: Output, - class: Unhandled, - details: UnknownDifference, }, ] 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 7b96447..908f776 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 -@@ -137,7 +137,7 @@ +@@ "SubType": { "properties": { "value": { @@ -14,19 +14,29 @@ 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", + paths: [ + ChangePath { + old: [ + "#/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: [ + "#/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, + }, ], - 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", + changes: [ + ChangeInfo { + old_subpath: "", + new_subpath: "", + message: "schema types changed", + class: Incompatible, + details: UnknownDifference, + }, ], - comparison: Output, - class: Incompatible, - details: UnknownDifference, }, ] diff --git a/tests/test_changes.rs b/tests/test_changes.rs index 05ad940..c222a9f 100644 --- a/tests/test_changes.rs +++ b/tests/test_changes.rs @@ -1,4 +1,6 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company + +use std::fmt::Write; use drift::compare; use similar::TextDiff; @@ -39,19 +41,31 @@ fn test_change() { json_patch::patch(&mut patched, &patch_value).unwrap(); let udiff = { - // Suppress the observation regarding the lack of a - // terminating newline. let mut base_pretty = serde_json::to_string_pretty(&base_value).unwrap(); base_pretty.push('\n'); let mut patched_pretty = serde_json::to_string_pretty(&patched).unwrap(); patched_pretty.push('\n'); let diff = TextDiff::from_lines(&base_pretty, &patched_pretty); - diff.unified_diff() - .header( - patch_entry.file_name().to_string_lossy().as_ref(), - "patched", - ) - .to_string() + let patch_name = patch_entry.file_name(); + + // Format the unified diff manually, replacing `@@ -N,M + // +N,M @@` hunk headers with bare `@@` to avoid churn + // when base.json changes shift line positions. + let mut out = String::new(); + let mut first = true; + for hunk in diff.unified_diff().iter_hunks() { + if first { + writeln!(out, "--- {}", patch_name.to_string_lossy()).unwrap(); + writeln!(out, "+++ patched").unwrap(); + first = false; + } + writeln!(out, "@@").unwrap(); + for change in hunk.iter_changes() { + write!(out, "{}{}", change.tag(), change.to_string_lossy()) + .unwrap(); + } + } + out }; let result =