Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 42 additions & 94 deletions src/change.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
// Copyright 2026 Oxide Computer Company

use std::fmt;

use crate::JsonPathStack;

// Describes any change detected between two OpenAPI documents.
/// A paired path through old and new documents that led to a change.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ChangePath {
/// Path in the old document.
pub old: JsonPathStack,
/// Path in the new document.
pub new: JsonPathStack,
/// Context in which this path was traversed (Input or Output).
pub comparison: ChangeComparison,
}

/// Describes changes detected within a single component or endpoint.
#[derive(Debug)]
pub struct Change {
/// All paths through which this component/endpoint was reached.
///
/// For schema changes, this may contain multiple paths if the same schema
/// is referenced from multiple locations. For endpoint changes, this will
/// typically contain a single path.
pub paths: Vec<ChangePath>,

/// Individual changes detected within this component/endpoint.
pub changes: Vec<ChangeInfo>,
}

/// A single change detected at a specific location within a component/endpoint.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ChangeInfo {
/// The relative path in the old document where the change occurred.
///
/// For example, `properties/name` for a change at
/// `#/components/schemas/User/properties/name`.
///
/// This is an empty string if the change is at the component/endpoint root
/// itself.
pub old_subpath: String,

/// The relative path in the new document where the change occurred.
pub new_subpath: String,

/// Human-readable message describing the nature of the change.
pub message: String,
/// The path in the old document where the change was detected.
pub old_path: JsonPathStack,
/// The path in the new document where the change was detected.
pub new_path: JsonPathStack,

/// The way in which the relevant structures during the comparison.
pub comparison: ChangeComparison,

/// Classification of the change compatibility.
pub class: ChangeClass,
Expand All @@ -23,88 +52,7 @@ pub struct 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,
old_path: JsonPathStack,
new_path: JsonPathStack,
comparison: ChangeComparison,
class: ChangeClass,
details: ChangeDetails,
) -> Self {
Self {
message: message.to_string(),
old_path,
new_path,
comparison,
class,
details,
}
}
}

#[derive(Debug)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChangeComparison {
// Inputs such as operation parameters and request bodies.
Input,
Expand All @@ -115,7 +63,7 @@ pub enum ChangeComparison {
Structural,
}

#[derive(Debug)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChangeClass {
BackwardIncompatible,
ForwardIncompatible,
Expand All @@ -124,7 +72,7 @@ pub enum ChangeClass {
Unhandled,
}

#[derive(Debug)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChangeDetails {
Metadata,
Added,
Expand Down
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.