Moved what-changed PropertyChanges to a pointer in all models.

No breaking changes, gofmt seems to have gone a little nuts for some reason, but this is an internal change that moves everything to a pointer, for better reflection use down the chain.
This commit is contained in:
Dave Shanley
2022-11-23 12:57:10 -05:00
parent 726134f00e
commit 0e0b99225d
33 changed files with 4339 additions and 4333 deletions

View File

@@ -4,97 +4,97 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// CallbackChanges represents all changes made between two Callback OpenAPI objects. // CallbackChanges represents all changes made between two Callback OpenAPI objects.
type CallbackChanges struct { type CallbackChanges struct {
PropertyChanges *PropertyChanges
ExpressionChanges map[string]*PathItemChanges `json:"expressions,omitempty" yaml:"expressions,omitempty"` ExpressionChanges map[string]*PathItemChanges `json:"expressions,omitempty" yaml:"expressions,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns a total count of all changes made between Callback objects // TotalChanges returns a total count of all changes made between Callback objects
func (c *CallbackChanges) TotalChanges() int { func (c *CallbackChanges) TotalChanges() int {
d := c.PropertyChanges.TotalChanges() d := c.PropertyChanges.TotalChanges()
for k := range c.ExpressionChanges { for k := range c.ExpressionChanges {
d += c.ExpressionChanges[k].TotalChanges() d += c.ExpressionChanges[k].TotalChanges()
} }
if c.ExtensionChanges != nil { if c.ExtensionChanges != nil {
d += c.ExtensionChanges.TotalChanges() d += c.ExtensionChanges.TotalChanges()
} }
return d return d
} }
// TotalBreakingChanges returns a total count of all changes made between Callback objects // TotalBreakingChanges returns a total count of all changes made between Callback objects
func (c *CallbackChanges) TotalBreakingChanges() int { func (c *CallbackChanges) TotalBreakingChanges() int {
d := c.PropertyChanges.TotalBreakingChanges() d := c.PropertyChanges.TotalBreakingChanges()
for k := range c.ExpressionChanges { for k := range c.ExpressionChanges {
d += c.ExpressionChanges[k].TotalBreakingChanges() d += c.ExpressionChanges[k].TotalBreakingChanges()
} }
if c.ExtensionChanges != nil { if c.ExtensionChanges != nil {
d += c.ExtensionChanges.TotalBreakingChanges() d += c.ExtensionChanges.TotalBreakingChanges()
} }
return d return d
} }
// CompareCallback will compare two Callback objects and return a pointer to CallbackChanges with all the things // CompareCallback will compare two Callback objects and return a pointer to CallbackChanges with all the things
// that have changed between them. // that have changed between them.
func CompareCallback(l, r *v3.Callback) *CallbackChanges { func CompareCallback(l, r *v3.Callback) *CallbackChanges {
cc := new(CallbackChanges) cc := new(CallbackChanges)
var changes []*Change var changes []*Change
lHashes := make(map[string]string) lHashes := make(map[string]string)
rHashes := make(map[string]string) rHashes := make(map[string]string)
lValues := make(map[string]low.ValueReference[*v3.PathItem]) lValues := make(map[string]low.ValueReference[*v3.PathItem])
rValues := make(map[string]low.ValueReference[*v3.PathItem]) rValues := make(map[string]low.ValueReference[*v3.PathItem])
for k := range l.Expression.Value { for k := range l.Expression.Value {
lHashes[k.Value] = low.GenerateHashString(l.Expression.Value[k].Value) lHashes[k.Value] = low.GenerateHashString(l.Expression.Value[k].Value)
lValues[k.Value] = l.Expression.Value[k] lValues[k.Value] = l.Expression.Value[k]
} }
for k := range r.Expression.Value { for k := range r.Expression.Value {
rHashes[k.Value] = low.GenerateHashString(r.Expression.Value[k].Value) rHashes[k.Value] = low.GenerateHashString(r.Expression.Value[k].Value)
rValues[k.Value] = r.Expression.Value[k] rValues[k.Value] = r.Expression.Value[k]
} }
expChanges := make(map[string]*PathItemChanges) expChanges := make(map[string]*PathItemChanges)
// check left path item hashes // check left path item hashes
for k := range lHashes { for k := range lHashes {
rhash := rHashes[k] rhash := rHashes[k]
if rhash == "" { if rhash == "" {
CreateChange(&changes, ObjectRemoved, k, CreateChange(&changes, ObjectRemoved, k,
lValues[k].GetValueNode(), nil, true, lValues[k].GetValueNode(), nil, true,
lValues[k].GetValue(), nil) lValues[k].GetValue(), nil)
continue continue
} }
if lHashes[k] == rHashes[k] { if lHashes[k] == rHashes[k] {
continue continue
} }
// run comparison. // run comparison.
expChanges[k] = ComparePathItems(lValues[k].Value, rValues[k].Value) expChanges[k] = ComparePathItems(lValues[k].Value, rValues[k].Value)
} }
//check right path item hashes //check right path item hashes
for k := range rHashes { for k := range rHashes {
lhash := lHashes[k] lhash := lHashes[k]
if lhash == "" { if lhash == "" {
CreateChange(&changes, ObjectAdded, k, CreateChange(&changes, ObjectAdded, k,
nil, rValues[k].GetValueNode(), false, nil, rValues[k].GetValueNode(), false,
nil, rValues[k].GetValue()) nil, rValues[k].GetValue())
continue continue
} }
} }
cc.ExpressionChanges = expChanges cc.ExpressionChanges = expChanges
cc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) cc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
cc.Changes = changes cc.PropertyChanges = NewPropertyChanges(changes)
if cc.TotalChanges() <= 0 { if cc.TotalChanges() <= 0 {
return nil return nil
} }
return cc return cc
} }

View File

@@ -4,42 +4,42 @@
package model package model
import ( import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// Definitions of the possible changes between two items // Definitions of the possible changes between two items
const ( const (
// Modified means that was a modification of a value was made // Modified means that was a modification of a value was made
Modified = iota + 1 Modified = iota + 1
// PropertyAdded means that a new property to an object was added // PropertyAdded means that a new property to an object was added
PropertyAdded PropertyAdded
// ObjectAdded means that a new object was added to a parent object // ObjectAdded means that a new object was added to a parent object
ObjectAdded ObjectAdded
// ObjectRemoved means that an object was removed from a parent object // ObjectRemoved means that an object was removed from a parent object
ObjectRemoved ObjectRemoved
// PropertyRemoved means that a property of an object was removed // PropertyRemoved means that a property of an object was removed
PropertyRemoved PropertyRemoved
) )
// WhatChanged is a summary object that contains a high level summary of everything changed. // WhatChanged is a summary object that contains a high level summary of everything changed.
type WhatChanged struct { type WhatChanged struct {
Added int `json:"added,omitempty" yaml:"added,omitempty"` Added int `json:"added,omitempty" yaml:"added,omitempty"`
Removed int `json:"removed,omitempty" yaml:"removed,omitempty"` Removed int `json:"removed,omitempty" yaml:"removed,omitempty"`
Modified int `json:"modified,omitempty" yaml:"modified,omitempty"` Modified int `json:"modified,omitempty" yaml:"modified,omitempty"`
TotalChanges int `json:"total,omitempty" yaml:"total,omitempty"` TotalChanges int `json:"total,omitempty" yaml:"total,omitempty"`
} }
// ChangeContext holds a reference to the line and column positions of original and new change. // ChangeContext holds a reference to the line and column positions of original and new change.
type ChangeContext struct { type ChangeContext struct {
OriginalLine *int `json:"originalLine,omitempty" yaml:"originalLine,omitempty"` OriginalLine *int `json:"originalLine,omitempty" yaml:"originalLine,omitempty"`
OriginalColumn *int `json:"originalColumn,omitempty" yaml:"originalColumn,omitempty"` OriginalColumn *int `json:"originalColumn,omitempty" yaml:"originalColumn,omitempty"`
NewLine *int `json:"newLine,omitempty" yaml:"newLine,omitempty"` NewLine *int `json:"newLine,omitempty" yaml:"newLine,omitempty"`
NewColumn *int `json:"newColumn,omitempty" yaml:"newColumn,omitempty"` NewColumn *int `json:"newColumn,omitempty" yaml:"newColumn,omitempty"`
} }
// HasChanged determines if the line and column numbers of the original and new values have changed. // HasChanged determines if the line and column numbers of the original and new values have changed.
@@ -47,66 +47,70 @@ type ChangeContext struct {
// It's worth noting that there is no guarantee to the positions of anything in either left or right, so // It's worth noting that there is no guarantee to the positions of anything in either left or right, so
// considering these values as 'changes' is going to add a considerable amount of noise to results. // considering these values as 'changes' is going to add a considerable amount of noise to results.
func (c *ChangeContext) HasChanged() bool { func (c *ChangeContext) HasChanged() bool {
if c.NewLine != nil && c.OriginalLine != nil && *c.NewLine != *c.OriginalLine { if c.NewLine != nil && c.OriginalLine != nil && *c.NewLine != *c.OriginalLine {
return true return true
} }
if c.NewColumn != nil && c.OriginalColumn != nil && *c.NewColumn != *c.OriginalColumn { if c.NewColumn != nil && c.OriginalColumn != nil && *c.NewColumn != *c.OriginalColumn {
return true return true
} }
if (c.NewLine == nil && c.OriginalLine != nil) || (c.NewLine != nil && c.OriginalLine == nil) { if (c.NewLine == nil && c.OriginalLine != nil) || (c.NewLine != nil && c.OriginalLine == nil) {
return true return true
} }
if (c.NewColumn == nil && c.OriginalColumn != nil) || (c.NewColumn != nil && c.OriginalColumn == nil) { if (c.NewColumn == nil && c.OriginalColumn != nil) || (c.NewColumn != nil && c.OriginalColumn == nil) {
return true return true
} }
return false return false
} }
// Change represents a change between two different elements inside an OpenAPI specification. // Change represents a change between two different elements inside an OpenAPI specification.
type Change struct { type Change struct {
// Context represents the lines and column numbers of the original and new values // Context represents the lines and column numbers of the original and new values
// It's worth noting that these values may frequently be different and are not used to calculate // It's worth noting that these values may frequently be different and are not used to calculate
// a change. If the positions change, but values do not, then no change is recorded. // a change. If the positions change, but values do not, then no change is recorded.
Context *ChangeContext `json:"context,omitempty" yaml:"context,omitempty"` Context *ChangeContext `json:"context,omitempty" yaml:"context,omitempty"`
// ChangeType represents the type of change that occurred. stored as an integer, defined by constants above. // ChangeType represents the type of change that occurred. stored as an integer, defined by constants above.
ChangeType int `json:"change,omitempty" yaml:"change,omitempty"` ChangeType int `json:"change,omitempty" yaml:"change,omitempty"`
// Property is the property name key being changed. // Property is the property name key being changed.
Property string `json:"property,omitempty" yaml:"property,omitempty"` Property string `json:"property,omitempty" yaml:"property,omitempty"`
// Original is the original value represented as a string. // Original is the original value represented as a string.
Original string `json:"original,omitempty" yaml:"original,omitempty"` Original string `json:"original,omitempty" yaml:"original,omitempty"`
// New is the new value represented as a string. // New is the new value represented as a string.
New string `json:"new,omitempty" yaml:"new,omitempty"` New string `json:"new,omitempty" yaml:"new,omitempty"`
// Breaking determines if the change is a breaking one or not. // Breaking determines if the change is a breaking one or not.
Breaking bool `json:"breaking" yaml:"breaking"` Breaking bool `json:"breaking" yaml:"breaking"`
// OriginalObject represents the original object that was changed. // OriginalObject represents the original object that was changed.
OriginalObject any `json:"-" yaml:"-"` OriginalObject any `json:"-" yaml:"-"`
// NewObject represents the new object that has been modified. // NewObject represents the new object that has been modified.
NewObject any `json:"-" yaml:"-"` NewObject any `json:"-" yaml:"-"`
} }
// PropertyChanges holds a slice of Change pointers // PropertyChanges holds a slice of Change pointers
type PropertyChanges struct { type PropertyChanges struct {
//Total *int `json:"total,omitempty" yaml:"total,omitempty"` //Total *int `json:"total,omitempty" yaml:"total,omitempty"`
//Breaking *int `json:"breaking,omitempty" yaml:"breaking,omitempty"` //Breaking *int `json:"breaking,omitempty" yaml:"breaking,omitempty"`
Changes []*Change `json:"changes,omitempty" yaml:"changes,omitempty"` Changes []*Change `json:"changes,omitempty" yaml:"changes,omitempty"`
} }
// TotalChanges returns the total number of property changes made. // TotalChanges returns the total number of property changes made.
func (p PropertyChanges) TotalChanges() int { func (p PropertyChanges) TotalChanges() int {
return len(p.Changes) return len(p.Changes)
} }
// TotalBreakingChanges returns the total number of property breaking changes made. // TotalBreakingChanges returns the total number of property breaking changes made.
func (p PropertyChanges) TotalBreakingChanges() int { func (p PropertyChanges) TotalBreakingChanges() int {
return CountBreakingChanges(p.Changes) return CountBreakingChanges(p.Changes)
}
func NewPropertyChanges(changes []*Change) *PropertyChanges {
return &PropertyChanges{Changes: changes}
} }
// SortByChangeType will order changes by the types of change they represent, // SortByChangeType will order changes by the types of change they represent,
@@ -120,24 +124,24 @@ func (p PropertyChanges) TotalBreakingChanges() int {
// PropertyCheck is used by functions to check the state of left and right values. // PropertyCheck is used by functions to check the state of left and right values.
type PropertyCheck struct { type PropertyCheck struct {
// Original is the property we're checking on the left // Original is the property we're checking on the left
Original any Original any
// New is s the property we're checking on the right // New is s the property we're checking on the right
New any New any
// Label is the identifier we're looking for on the left and right hand sides // Label is the identifier we're looking for on the left and right hand sides
Label string Label string
// LeftNode is the yaml.Node pointer that holds the original node structure of the value // LeftNode is the yaml.Node pointer that holds the original node structure of the value
LeftNode *yaml.Node LeftNode *yaml.Node
// RightNode is the yaml.Node pointer that holds the new node structure of the value // RightNode is the yaml.Node pointer that holds the new node structure of the value
RightNode *yaml.Node RightNode *yaml.Node
// Breaking determines if the check is a breaking change (modifications or removals etc.) // Breaking determines if the check is a breaking change (modifications or removals etc.)
Breaking bool Breaking bool
// Changes represents a pointer to the slice to contain all changes found. // Changes represents a pointer to the slice to contain all changes found.
Changes *[]*Change Changes *[]*Change
} }

View File

@@ -4,11 +4,11 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
) )
// ComponentsChanges represents changes made to both OpenAPI and Swagger documents. This model is based on OpenAPI 3 // ComponentsChanges represents changes made to both OpenAPI and Swagger documents. This model is based on OpenAPI 3
@@ -35,228 +35,228 @@ import (
// modifications are not checked, these checks occur in-place by implementing objects as they are autp-resolved // modifications are not checked, these checks occur in-place by implementing objects as they are autp-resolved
// when the model is built. // when the model is built.
type ComponentsChanges struct { type ComponentsChanges struct {
PropertyChanges *PropertyChanges
SchemaChanges map[string]*SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"` SchemaChanges map[string]*SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"`
SecuritySchemeChanges map[string]*SecuritySchemeChanges `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` SecuritySchemeChanges map[string]*SecuritySchemeChanges `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// CompareComponents will compare OpenAPI components for any changes. Accepts Swagger Definition objects // CompareComponents will compare OpenAPI components for any changes. Accepts Swagger Definition objects
// like ParameterDefinitions or Definitions etc. // like ParameterDefinitions or Definitions etc.
func CompareComponents(l, r any) *ComponentsChanges { func CompareComponents(l, r any) *ComponentsChanges {
var changes []*Change var changes []*Change
cc := new(ComponentsChanges) cc := new(ComponentsChanges)
// Swagger Parameters // Swagger Parameters
if reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.ParameterDefinitions) lDef := l.(*v2.ParameterDefinitions)
rDef := r.(*v2.ParameterDefinitions) rDef := r.(*v2.ParameterDefinitions)
var a, b map[low.KeyReference[string]]low.ValueReference[*v2.Parameter] var a, b map[low.KeyReference[string]]low.ValueReference[*v2.Parameter]
if lDef != nil { if lDef != nil {
a = lDef.Definitions a = lDef.Definitions
} }
if rDef != nil { if rDef != nil {
b = rDef.Definitions b = rDef.Definitions
} }
CheckMapForAdditionRemoval(a, b, &changes, v3.ParametersLabel) CheckMapForAdditionRemoval(a, b, &changes, v3.ParametersLabel)
} }
// Swagger Responses // Swagger Responses
if reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.ResponsesDefinitions) lDef := l.(*v2.ResponsesDefinitions)
rDef := r.(*v2.ResponsesDefinitions) rDef := r.(*v2.ResponsesDefinitions)
var a, b map[low.KeyReference[string]]low.ValueReference[*v2.Response] var a, b map[low.KeyReference[string]]low.ValueReference[*v2.Response]
if lDef != nil { if lDef != nil {
a = lDef.Definitions a = lDef.Definitions
} }
if rDef != nil { if rDef != nil {
b = rDef.Definitions b = rDef.Definitions
} }
CheckMapForAdditionRemoval(a, b, &changes, v3.ResponsesLabel) CheckMapForAdditionRemoval(a, b, &changes, v3.ResponsesLabel)
} }
// Swagger Schemas // Swagger Schemas
if reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.Definitions) lDef := l.(*v2.Definitions)
rDef := r.(*v2.Definitions) rDef := r.(*v2.Definitions)
var a, b map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy] var a, b map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy]
if lDef != nil { if lDef != nil {
a = lDef.Schemas a = lDef.Schemas
} }
if rDef != nil { if rDef != nil {
b = rDef.Schemas b = rDef.Schemas
} }
cc.SchemaChanges = CheckMapForChanges(a, b, &changes, v2.DefinitionsLabel, CompareSchemas) cc.SchemaChanges = CheckMapForChanges(a, b, &changes, v2.DefinitionsLabel, CompareSchemas)
} }
// Swagger Security Definitions // Swagger Security Definitions
if reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.SecurityDefinitions) lDef := l.(*v2.SecurityDefinitions)
rDef := r.(*v2.SecurityDefinitions) rDef := r.(*v2.SecurityDefinitions)
var a, b map[low.KeyReference[string]]low.ValueReference[*v2.SecurityScheme] var a, b map[low.KeyReference[string]]low.ValueReference[*v2.SecurityScheme]
if lDef != nil { if lDef != nil {
a = lDef.Definitions a = lDef.Definitions
} }
if rDef != nil { if rDef != nil {
b = rDef.Definitions b = rDef.Definitions
} }
cc.SecuritySchemeChanges = CheckMapForChanges(a, b, &changes, cc.SecuritySchemeChanges = CheckMapForChanges(a, b, &changes,
v3.SecurityDefinitionLabel, CompareSecuritySchemesV2) v3.SecurityDefinitionLabel, CompareSecuritySchemesV2)
} }
// OpenAPI Components // OpenAPI Components
if reflect.TypeOf(&v3.Components{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v3.Components{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v3.Components{}) == reflect.TypeOf(r) { reflect.TypeOf(&v3.Components{}) == reflect.TypeOf(r) {
lComponents := l.(*v3.Components) lComponents := l.(*v3.Components)
rComponents := r.(*v3.Components) rComponents := r.(*v3.Components)
//if low.AreEqual(lComponents, rComponents) { //if low.AreEqual(lComponents, rComponents) {
// return nil // return nil
//} //}
doneChan := make(chan componentComparison) doneChan := make(chan componentComparison)
comparisons := 0 comparisons := 0
// run as fast as we can, thread all the things. // run as fast as we can, thread all the things.
if !lComponents.Schemas.IsEmpty() || !rComponents.Schemas.IsEmpty() { if !lComponents.Schemas.IsEmpty() || !rComponents.Schemas.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Schemas.Value, rComponents.Schemas.Value, go runComparison(lComponents.Schemas.Value, rComponents.Schemas.Value,
&changes, v3.SchemasLabel, CompareSchemas, doneChan) &changes, v3.SchemasLabel, CompareSchemas, doneChan)
} }
if !lComponents.Responses.IsEmpty() || !rComponents.Responses.IsEmpty() { if !lComponents.Responses.IsEmpty() || !rComponents.Responses.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Responses.Value, rComponents.Responses.Value, go runComparison(lComponents.Responses.Value, rComponents.Responses.Value,
&changes, v3.ResponsesLabel, CompareResponseV3, doneChan) &changes, v3.ResponsesLabel, CompareResponseV3, doneChan)
} }
if !lComponents.Parameters.IsEmpty() || !rComponents.Parameters.IsEmpty() { if !lComponents.Parameters.IsEmpty() || !rComponents.Parameters.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Parameters.Value, rComponents.Parameters.Value, go runComparison(lComponents.Parameters.Value, rComponents.Parameters.Value,
&changes, v3.ParametersLabel, CompareParametersV3, doneChan) &changes, v3.ParametersLabel, CompareParametersV3, doneChan)
} }
if !lComponents.Examples.IsEmpty() || !rComponents.Examples.IsEmpty() { if !lComponents.Examples.IsEmpty() || !rComponents.Examples.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Examples.Value, rComponents.Examples.Value, go runComparison(lComponents.Examples.Value, rComponents.Examples.Value,
&changes, v3.ExamplesLabel, CompareExamples, doneChan) &changes, v3.ExamplesLabel, CompareExamples, doneChan)
} }
if !lComponents.RequestBodies.IsEmpty() || !rComponents.RequestBodies.IsEmpty() { if !lComponents.RequestBodies.IsEmpty() || !rComponents.RequestBodies.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.RequestBodies.Value, rComponents.RequestBodies.Value, go runComparison(lComponents.RequestBodies.Value, rComponents.RequestBodies.Value,
&changes, v3.RequestBodiesLabel, CompareRequestBodies, doneChan) &changes, v3.RequestBodiesLabel, CompareRequestBodies, doneChan)
} }
if !lComponents.Headers.IsEmpty() || !rComponents.Headers.IsEmpty() { if !lComponents.Headers.IsEmpty() || !rComponents.Headers.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Headers.Value, rComponents.Headers.Value, go runComparison(lComponents.Headers.Value, rComponents.Headers.Value,
&changes, v3.HeadersLabel, CompareHeadersV3, doneChan) &changes, v3.HeadersLabel, CompareHeadersV3, doneChan)
} }
if !lComponents.SecuritySchemes.IsEmpty() || !rComponents.SecuritySchemes.IsEmpty() { if !lComponents.SecuritySchemes.IsEmpty() || !rComponents.SecuritySchemes.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.SecuritySchemes.Value, rComponents.SecuritySchemes.Value, go runComparison(lComponents.SecuritySchemes.Value, rComponents.SecuritySchemes.Value,
&changes, v3.SecuritySchemesLabel, CompareSecuritySchemesV3, doneChan) &changes, v3.SecuritySchemesLabel, CompareSecuritySchemesV3, doneChan)
} }
if !lComponents.Links.IsEmpty() || !rComponents.Links.IsEmpty() { if !lComponents.Links.IsEmpty() || !rComponents.Links.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Links.Value, rComponents.Links.Value, go runComparison(lComponents.Links.Value, rComponents.Links.Value,
&changes, v3.LinksLabel, CompareLinks, doneChan) &changes, v3.LinksLabel, CompareLinks, doneChan)
} }
if !lComponents.Callbacks.IsEmpty() || !rComponents.Callbacks.IsEmpty() { if !lComponents.Callbacks.IsEmpty() || !rComponents.Callbacks.IsEmpty() {
comparisons++ comparisons++
go runComparison(lComponents.Callbacks.Value, rComponents.Callbacks.Value, go runComparison(lComponents.Callbacks.Value, rComponents.Callbacks.Value,
&changes, v3.CallbacksLabel, CompareCallback, doneChan) &changes, v3.CallbacksLabel, CompareCallback, doneChan)
} }
cc.ExtensionChanges = CompareExtensions(lComponents.Extensions, rComponents.Extensions) cc.ExtensionChanges = CompareExtensions(lComponents.Extensions, rComponents.Extensions)
completedComponents := 0 completedComponents := 0
for completedComponents < comparisons { for completedComponents < comparisons {
select { select {
case res := <-doneChan: case res := <-doneChan:
switch res.prop { switch res.prop {
case v3.SchemasLabel: case v3.SchemasLabel:
completedComponents++ completedComponents++
cc.SchemaChanges = res.result.(map[string]*SchemaChanges) cc.SchemaChanges = res.result.(map[string]*SchemaChanges)
break break
case v3.SecuritySchemesLabel: case v3.SecuritySchemesLabel:
completedComponents++ completedComponents++
cc.SecuritySchemeChanges = res.result.(map[string]*SecuritySchemeChanges) cc.SecuritySchemeChanges = res.result.(map[string]*SecuritySchemeChanges)
break break
case v3.ResponsesLabel, v3.ParametersLabel, v3.ExamplesLabel, v3.RequestBodiesLabel, v3.HeadersLabel, case v3.ResponsesLabel, v3.ParametersLabel, v3.ExamplesLabel, v3.RequestBodiesLabel, v3.HeadersLabel,
v3.LinksLabel, v3.CallbacksLabel: v3.LinksLabel, v3.CallbacksLabel:
completedComponents++ completedComponents++
break break
} }
} }
} }
} }
cc.Changes = changes cc.PropertyChanges = NewPropertyChanges(changes)
if cc.TotalChanges() <= 0 { if cc.TotalChanges() <= 0 {
return nil return nil
} }
return cc return cc
} }
type componentComparison struct { type componentComparison struct {
prop string prop string
result any result any
} }
// run a generic comparison in a thread which in turn splits checks into further threads. // run a generic comparison in a thread which in turn splits checks into further threads.
func runComparison[T any, R any](l, r map[low.KeyReference[string]]low.ValueReference[T], func runComparison[T any, R any](l, r map[low.KeyReference[string]]low.ValueReference[T],
changes *[]*Change, label string, compareFunc func(l, r T) R, doneChan chan componentComparison) { changes *[]*Change, label string, compareFunc func(l, r T) R, doneChan chan componentComparison) {
// for schemas // for schemas
if label == v3.SchemasLabel || label == v2.DefinitionsLabel || label == v3.SecuritySchemesLabel { if label == v3.SchemasLabel || label == v2.DefinitionsLabel || label == v3.SecuritySchemesLabel {
doneChan <- componentComparison{ doneChan <- componentComparison{
prop: label, prop: label,
result: CheckMapForChanges(l, r, changes, label, compareFunc), result: CheckMapForChanges(l, r, changes, label, compareFunc),
} }
return return
} else { } else {
doneChan <- componentComparison{ doneChan <- componentComparison{
prop: label, prop: label,
result: CheckMapForAdditionRemoval(l, r, changes, label), result: CheckMapForAdditionRemoval(l, r, changes, label),
} }
} }
} }
// TotalChanges returns total changes for all Components and Definitions // TotalChanges returns total changes for all Components and Definitions
func (c *ComponentsChanges) TotalChanges() int { func (c *ComponentsChanges) TotalChanges() int {
v := c.PropertyChanges.TotalChanges() v := c.PropertyChanges.TotalChanges()
for k := range c.SchemaChanges { for k := range c.SchemaChanges {
v += c.SchemaChanges[k].TotalChanges() v += c.SchemaChanges[k].TotalChanges()
} }
for k := range c.SecuritySchemeChanges { for k := range c.SecuritySchemeChanges {
v += c.SecuritySchemeChanges[k].TotalChanges() v += c.SecuritySchemeChanges[k].TotalChanges()
} }
if c.ExtensionChanges != nil { if c.ExtensionChanges != nil {
v += c.ExtensionChanges.TotalChanges() v += c.ExtensionChanges.TotalChanges()
} }
return v return v
} }
// TotalBreakingChanges returns all breaking changes found for all Components and Definitions // TotalBreakingChanges returns all breaking changes found for all Components and Definitions
func (c *ComponentsChanges) TotalBreakingChanges() int { func (c *ComponentsChanges) TotalBreakingChanges() int {
v := c.PropertyChanges.TotalBreakingChanges() v := c.PropertyChanges.TotalBreakingChanges()
for k := range c.SchemaChanges { for k := range c.SchemaChanges {
v += c.SchemaChanges[k].TotalBreakingChanges() v += c.SchemaChanges[k].TotalBreakingChanges()
} }
for k := range c.SecuritySchemeChanges { for k := range c.SecuritySchemeChanges {
v += c.SecuritySchemeChanges[k].TotalBreakingChanges() v += c.SecuritySchemeChanges[k].TotalBreakingChanges()
} }
return v return v
} }

View File

@@ -4,23 +4,23 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// ContactChanges Represent changes to a Contact object that is a child of Info, part of an OpenAPI document. // ContactChanges Represent changes to a Contact object that is a child of Info, part of an OpenAPI document.
type ContactChanges struct { type ContactChanges struct {
PropertyChanges *PropertyChanges
} }
// TotalChanges represents the total number of changes that have occurred to a Contact object // TotalChanges represents the total number of changes that have occurred to a Contact object
func (c *ContactChanges) TotalChanges() int { func (c *ContactChanges) TotalChanges() int {
return c.PropertyChanges.TotalChanges() return c.PropertyChanges.TotalChanges()
} }
// TotalBreakingChanges always returns 0 for Contact objects, they are non-binding. // TotalBreakingChanges always returns 0 for Contact objects, they are non-binding.
func (c *ContactChanges) TotalBreakingChanges() int { func (c *ContactChanges) TotalBreakingChanges() int {
return 0 return 0
} }
// CompareContact will check a left (original) and right (new) Contact object for any changes. If there // CompareContact will check a left (original) and right (new) Contact object for any changes. If there
@@ -28,49 +28,49 @@ func (c *ContactChanges) TotalBreakingChanges() int {
// returns nil. // returns nil.
func CompareContact(l, r *base.Contact) *ContactChanges { func CompareContact(l, r *base.Contact) *ContactChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// check URL // check URL
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.URL.ValueNode, LeftNode: l.URL.ValueNode,
RightNode: r.URL.ValueNode, RightNode: r.URL.ValueNode,
Label: v3.URLLabel, Label: v3.URLLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check name // check name
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Name.ValueNode, LeftNode: l.Name.ValueNode,
RightNode: r.Name.ValueNode, RightNode: r.Name.ValueNode,
Label: v3.NameLabel, Label: v3.NameLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check email // check email
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Email.ValueNode, LeftNode: l.Email.ValueNode,
RightNode: r.Email.ValueNode, RightNode: r.Email.ValueNode,
Label: v3.EmailLabel, Label: v3.EmailLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check everything. // check everything.
CheckProperties(props) CheckProperties(props)
dc := new(ContactChanges) dc := new(ContactChanges)
dc.Changes = changes dc.PropertyChanges = NewPropertyChanges(changes)
if dc.TotalChanges() <= 0 { if dc.TotalChanges() <= 0 {
return nil return nil
} }
return dc return dc
} }

View File

@@ -4,83 +4,83 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// DiscriminatorChanges represents changes made to a Discriminator OpenAPI object // DiscriminatorChanges represents changes made to a Discriminator OpenAPI object
type DiscriminatorChanges struct { type DiscriminatorChanges struct {
PropertyChanges *PropertyChanges
MappingChanges []*Change `json:"mappings,omitempty" yaml:"mappings,omitempty"` MappingChanges []*Change `json:"mappings,omitempty" yaml:"mappings,omitempty"`
} }
// TotalChanges returns a count of everything changed within the Discriminator object // TotalChanges returns a count of everything changed within the Discriminator object
func (d *DiscriminatorChanges) TotalChanges() int { func (d *DiscriminatorChanges) TotalChanges() int {
l := 0 l := 0
if k := d.PropertyChanges.TotalChanges(); k > 0 { if k := d.PropertyChanges.TotalChanges(); k > 0 {
l += k l += k
} }
if k := len(d.MappingChanges); k > 0 { if k := len(d.MappingChanges); k > 0 {
l += k l += k
} }
return l return l
} }
// TotalBreakingChanges returns the number of breaking changes made by the Discriminator // TotalBreakingChanges returns the number of breaking changes made by the Discriminator
func (d *DiscriminatorChanges) TotalBreakingChanges() int { func (d *DiscriminatorChanges) TotalBreakingChanges() int {
return d.PropertyChanges.TotalBreakingChanges() + CountBreakingChanges(d.MappingChanges) return d.PropertyChanges.TotalBreakingChanges() + CountBreakingChanges(d.MappingChanges)
} }
// CompareDiscriminator will check a left (original) and right (new) Discriminator object for changes // CompareDiscriminator will check a left (original) and right (new) Discriminator object for changes
// and will return a pointer to DiscriminatorChanges // and will return a pointer to DiscriminatorChanges
func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges { func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges {
dc := new(DiscriminatorChanges) dc := new(DiscriminatorChanges)
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
var mappingChanges []*Change var mappingChanges []*Change
// Name (breaking change) // Name (breaking change)
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.PropertyName.ValueNode, LeftNode: l.PropertyName.ValueNode,
RightNode: r.PropertyName.ValueNode, RightNode: r.PropertyName.ValueNode,
Label: v3.PropertyNameLabel, Label: v3.PropertyNameLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// check properties // check properties
CheckProperties(props) CheckProperties(props)
// flatten maps // flatten maps
lMap := FlattenLowLevelMap[string](l.Mapping) lMap := FlattenLowLevelMap[string](l.Mapping)
rMap := FlattenLowLevelMap[string](r.Mapping) rMap := FlattenLowLevelMap[string](r.Mapping)
// check for removals, modifications and moves // check for removals, modifications and moves
for i := range lMap { for i := range lMap {
CheckForObjectAdditionOrRemoval[string](lMap, rMap, i, &mappingChanges, false, true) CheckForObjectAdditionOrRemoval[string](lMap, rMap, i, &mappingChanges, false, true)
// if the existing tag exists, let's check it. // if the existing tag exists, let's check it.
if rMap[i] != nil { if rMap[i] != nil {
if lMap[i].Value != rMap[i].Value { if lMap[i].Value != rMap[i].Value {
CreateChange(&mappingChanges, Modified, i, lMap[i].GetValueNode(), CreateChange(&mappingChanges, Modified, i, lMap[i].GetValueNode(),
rMap[i].GetValueNode(), true, lMap[i].GetValue(), rMap[i].GetValue()) rMap[i].GetValueNode(), true, lMap[i].GetValue(), rMap[i].GetValue())
} }
} }
} }
for i := range rMap { for i := range rMap {
if lMap[i] == nil { if lMap[i] == nil {
CreateChange(&mappingChanges, ObjectAdded, i, nil, CreateChange(&mappingChanges, ObjectAdded, i, nil,
rMap[i].GetValueNode(), false, nil, rMap[i].GetValue()) rMap[i].GetValueNode(), false, nil, rMap[i].GetValue())
} }
} }
dc.Changes = changes dc.PropertyChanges = NewPropertyChanges(changes)
dc.MappingChanges = mappingChanges dc.MappingChanges = mappingChanges
if dc.TotalChanges() <= 0 { if dc.TotalChanges() <= 0 {
return nil return nil
} }
return dc return dc
} }

View File

@@ -11,278 +11,279 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
) )
// DocumentChanges represents all the changes made to an OpenAPI document. // DocumentChanges represents all the changes made to an OpenAPI document.
type DocumentChanges struct { type DocumentChanges struct {
PropertyChanges *PropertyChanges
InfoChanges *InfoChanges `json:"info,omitempty" yaml:"info,omitempty"` InfoChanges *InfoChanges `json:"info,omitempty" yaml:"info,omitempty"`
PathsChanges *PathsChanges `json:"paths,omitempty" yaml:"paths,omitempty"` PathsChanges *PathsChanges `json:"paths,omitempty" yaml:"paths,omitempty"`
TagChanges []*TagChanges `json:"tags,omitempty" yaml:"tags,omitempty"` TagChanges []*TagChanges `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocChanges *ExternalDocChanges `json:"externalDoc,omitempty" yaml:"externalDoc,omitempty"` ExternalDocChanges *ExternalDocChanges `json:"externalDoc,omitempty" yaml:"externalDoc,omitempty"`
WebhookChanges map[string]*PathItemChanges `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` WebhookChanges map[string]*PathItemChanges `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
ServerChanges []*ServerChanges `json:"servers,omitempty" yaml:"servers,omitempty"` ServerChanges []*ServerChanges `json:"servers,omitempty" yaml:"servers,omitempty"`
SecurityRequirementChanges []*SecurityRequirementChanges `json:"securityRequirements,omitempty" yaml:"securityRequirements,omitempty"` SecurityRequirementChanges []*SecurityRequirementChanges `json:"securityRequirements,omitempty" yaml:"securityRequirements,omitempty"`
ComponentsChanges *ComponentsChanges `json:"components,omitempty" yaml:"components,omitempty"` ComponentsChanges *ComponentsChanges `json:"components,omitempty" yaml:"components,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns a total count of all changes made in the Document // TotalChanges returns a total count of all changes made in the Document
func (d *DocumentChanges) TotalChanges() int { func (d *DocumentChanges) TotalChanges() int {
c := d.PropertyChanges.TotalChanges() c := d.PropertyChanges.TotalChanges()
if d.InfoChanges != nil { if d.InfoChanges != nil {
c += d.InfoChanges.TotalChanges() c += d.InfoChanges.TotalChanges()
} }
if d.PathsChanges != nil { if d.PathsChanges != nil {
c += d.PathsChanges.TotalChanges() c += d.PathsChanges.TotalChanges()
} }
for k := range d.TagChanges { for k := range d.TagChanges {
c += d.TagChanges[k].TotalChanges() c += d.TagChanges[k].TotalChanges()
} }
if d.ExternalDocChanges != nil { if d.ExternalDocChanges != nil {
c += d.ExternalDocChanges.TotalChanges() c += d.ExternalDocChanges.TotalChanges()
} }
for k := range d.WebhookChanges { for k := range d.WebhookChanges {
c += d.WebhookChanges[k].TotalChanges() c += d.WebhookChanges[k].TotalChanges()
} }
for k := range d.ServerChanges { for k := range d.ServerChanges {
c += d.ServerChanges[k].TotalChanges() c += d.ServerChanges[k].TotalChanges()
} }
for k := range d.SecurityRequirementChanges { for k := range d.SecurityRequirementChanges {
c += d.SecurityRequirementChanges[k].TotalChanges() c += d.SecurityRequirementChanges[k].TotalChanges()
} }
if d.ComponentsChanges != nil { if d.ComponentsChanges != nil {
c += d.ComponentsChanges.TotalChanges() c += d.ComponentsChanges.TotalChanges()
} }
if d.ExtensionChanges != nil { if d.ExtensionChanges != nil {
c += d.ExtensionChanges.TotalChanges() c += d.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns a total count of all breaking changes made in the Document // TotalBreakingChanges returns a total count of all breaking changes made in the Document
func (d *DocumentChanges) TotalBreakingChanges() int { func (d *DocumentChanges) TotalBreakingChanges() int {
c := d.PropertyChanges.TotalBreakingChanges() c := d.PropertyChanges.TotalBreakingChanges()
if d.InfoChanges != nil { if d.InfoChanges != nil {
c += d.InfoChanges.TotalBreakingChanges() c += d.InfoChanges.TotalBreakingChanges()
} }
if d.PathsChanges != nil { if d.PathsChanges != nil {
c += d.PathsChanges.TotalBreakingChanges() c += d.PathsChanges.TotalBreakingChanges()
} }
for k := range d.TagChanges { for k := range d.TagChanges {
c += d.TagChanges[k].TotalBreakingChanges() c += d.TagChanges[k].TotalBreakingChanges()
} }
if d.ExternalDocChanges != nil { if d.ExternalDocChanges != nil {
c += d.ExternalDocChanges.TotalBreakingChanges() c += d.ExternalDocChanges.TotalBreakingChanges()
} }
for k := range d.WebhookChanges { for k := range d.WebhookChanges {
c += d.WebhookChanges[k].TotalBreakingChanges() c += d.WebhookChanges[k].TotalBreakingChanges()
} }
for k := range d.ServerChanges { for k := range d.ServerChanges {
c += d.ServerChanges[k].TotalBreakingChanges() c += d.ServerChanges[k].TotalBreakingChanges()
} }
for k := range d.SecurityRequirementChanges { for k := range d.SecurityRequirementChanges {
c += d.SecurityRequirementChanges[k].TotalBreakingChanges() c += d.SecurityRequirementChanges[k].TotalBreakingChanges()
} }
if d.ComponentsChanges != nil { if d.ComponentsChanges != nil {
c += d.ComponentsChanges.TotalBreakingChanges() c += d.ComponentsChanges.TotalBreakingChanges()
} }
return c return c
} }
// CompareDocuments will compare any two OpenAPI documents (either Swagger or OpenAPI) and return a pointer to // CompareDocuments will compare any two OpenAPI documents (either Swagger or OpenAPI) and return a pointer to
// DocumentChanges that outlines everything that was found to have changed. // DocumentChanges that outlines everything that was found to have changed.
func CompareDocuments(l, r any) *DocumentChanges { func CompareDocuments(l, r any) *DocumentChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
dc := new(DocumentChanges) dc := new(DocumentChanges)
if reflect.TypeOf(&v2.Swagger{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Swagger{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v2.Swagger{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Swagger{}) == reflect.TypeOf(r) {
lDoc := l.(*v2.Swagger) lDoc := l.(*v2.Swagger)
rDoc := r.(*v2.Swagger) rDoc := r.(*v2.Swagger)
// version // version
addPropertyCheck(&props, lDoc.Swagger.ValueNode, rDoc.Swagger.ValueNode, addPropertyCheck(&props, lDoc.Swagger.ValueNode, rDoc.Swagger.ValueNode,
lDoc.Swagger.Value, rDoc.Swagger.Value, &changes, v3.SwaggerLabel, true) lDoc.Swagger.Value, rDoc.Swagger.Value, &changes, v3.SwaggerLabel, true)
// host // host
addPropertyCheck(&props, lDoc.Host.ValueNode, rDoc.Host.ValueNode, addPropertyCheck(&props, lDoc.Host.ValueNode, rDoc.Host.ValueNode,
lDoc.Host.Value, rDoc.Host.Value, &changes, v3.HostLabel, true) lDoc.Host.Value, rDoc.Host.Value, &changes, v3.HostLabel, true)
// base path // base path
addPropertyCheck(&props, lDoc.BasePath.ValueNode, rDoc.BasePath.ValueNode, addPropertyCheck(&props, lDoc.BasePath.ValueNode, rDoc.BasePath.ValueNode,
lDoc.BasePath.Value, rDoc.BasePath.Value, &changes, v3.BasePathLabel, true) lDoc.BasePath.Value, rDoc.BasePath.Value, &changes, v3.BasePathLabel, true)
// schemes // schemes
if len(lDoc.Schemes.Value) > 0 || len(lDoc.Schemes.Value) > 0 { if len(lDoc.Schemes.Value) > 0 || len(lDoc.Schemes.Value) > 0 {
ExtractStringValueSliceChanges(lDoc.Schemes.Value, rDoc.Schemes.Value, ExtractStringValueSliceChanges(lDoc.Schemes.Value, rDoc.Schemes.Value,
&changes, v3.SchemesLabel, true) &changes, v3.SchemesLabel, true)
} }
// consumes // consumes
if len(lDoc.Consumes.Value) > 0 || len(lDoc.Consumes.Value) > 0 { if len(lDoc.Consumes.Value) > 0 || len(lDoc.Consumes.Value) > 0 {
ExtractStringValueSliceChanges(lDoc.Consumes.Value, rDoc.Consumes.Value, ExtractStringValueSliceChanges(lDoc.Consumes.Value, rDoc.Consumes.Value,
&changes, v3.ConsumesLabel, true) &changes, v3.ConsumesLabel, true)
} }
// produces // produces
if len(lDoc.Produces.Value) > 0 || len(lDoc.Produces.Value) > 0 { if len(lDoc.Produces.Value) > 0 || len(lDoc.Produces.Value) > 0 {
ExtractStringValueSliceChanges(lDoc.Produces.Value, rDoc.Produces.Value, ExtractStringValueSliceChanges(lDoc.Produces.Value, rDoc.Produces.Value,
&changes, v3.ProducesLabel, true) &changes, v3.ProducesLabel, true)
} }
// tags // tags
dc.TagChanges = CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) dc.TagChanges = CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// paths // paths
if !lDoc.Paths.IsEmpty() || !rDoc.Paths.IsEmpty() { if !lDoc.Paths.IsEmpty() || !rDoc.Paths.IsEmpty() {
dc.PathsChanges = ComparePaths(lDoc.Paths.Value, rDoc.Paths.Value) dc.PathsChanges = ComparePaths(lDoc.Paths.Value, rDoc.Paths.Value)
} }
// external docs // external docs
compareDocumentExternalDocs(lDoc, rDoc, dc, &changes) compareDocumentExternalDocs(lDoc, rDoc, dc, &changes)
// info // info
compareDocumentInfo(&lDoc.Info, &rDoc.Info, dc, &changes) compareDocumentInfo(&lDoc.Info, &rDoc.Info, dc, &changes)
// security // security
if !lDoc.Security.IsEmpty() || !rDoc.Security.IsEmpty() { if !lDoc.Security.IsEmpty() || !rDoc.Security.IsEmpty() {
checkSecurity(lDoc.Security, rDoc.Security, &changes, dc) checkSecurity(lDoc.Security, rDoc.Security, &changes, dc)
} }
// components / definitions // components / definitions
// swagger (damn you) decided to put all this stuff at the document root, rather than cleanly // swagger (damn you) decided to put all this stuff at the document root, rather than cleanly
// placing it under a parent, like they did with OpenAPI. This means picking through each definition // placing it under a parent, like they did with OpenAPI. This means picking through each definition
// creating a new set of changes and then morphing them into a single changes object. // creating a new set of changes and then morphing them into a single changes object.
cc := new(ComponentsChanges) cc := new(ComponentsChanges)
if n := CompareComponents(lDoc.Definitions.Value, rDoc.Definitions.Value); n != nil { cc.PropertyChanges = new(PropertyChanges)
cc.SchemaChanges = n.SchemaChanges if n := CompareComponents(lDoc.Definitions.Value, rDoc.Definitions.Value); n != nil {
} cc.SchemaChanges = n.SchemaChanges
if n := CompareComponents(lDoc.SecurityDefinitions.Value, rDoc.SecurityDefinitions.Value); n != nil { }
cc.SecuritySchemeChanges = n.SecuritySchemeChanges if n := CompareComponents(lDoc.SecurityDefinitions.Value, rDoc.SecurityDefinitions.Value); n != nil {
} cc.SecuritySchemeChanges = n.SecuritySchemeChanges
if n := CompareComponents(lDoc.Parameters.Value, rDoc.Parameters.Value); n != nil { }
cc.Changes = append(cc.Changes, n.Changes...) if n := CompareComponents(lDoc.Parameters.Value, rDoc.Parameters.Value); n != nil {
} cc.PropertyChanges.Changes = append(cc.PropertyChanges.Changes, n.Changes...)
if n := CompareComponents(lDoc.Responses.Value, rDoc.Responses.Value); n != nil { }
cc.Changes = append(cc.Changes, n.Changes...) if n := CompareComponents(lDoc.Responses.Value, rDoc.Responses.Value); n != nil {
} cc.Changes = append(cc.Changes, n.Changes...)
dc.ExtensionChanges = CompareExtensions(lDoc.Extensions, rDoc.Extensions) }
if cc.TotalChanges() > 0 { dc.ExtensionChanges = CompareExtensions(lDoc.Extensions, rDoc.Extensions)
dc.ComponentsChanges = cc if cc.TotalChanges() > 0 {
} dc.ComponentsChanges = cc
} }
}
if reflect.TypeOf(&v3.Document{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Document{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v3.Document{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Document{}) == reflect.TypeOf(r) {
lDoc := l.(*v3.Document) lDoc := l.(*v3.Document)
rDoc := r.(*v3.Document) rDoc := r.(*v3.Document)
// version // version
addPropertyCheck(&props, lDoc.Version.ValueNode, rDoc.Version.ValueNode, addPropertyCheck(&props, lDoc.Version.ValueNode, rDoc.Version.ValueNode,
lDoc.Version.Value, rDoc.Version.Value, &changes, v3.OpenAPILabel, true) lDoc.Version.Value, rDoc.Version.Value, &changes, v3.OpenAPILabel, true)
// schema dialect // schema dialect
addPropertyCheck(&props, lDoc.JsonSchemaDialect.ValueNode, rDoc.JsonSchemaDialect.ValueNode, addPropertyCheck(&props, lDoc.JsonSchemaDialect.ValueNode, rDoc.JsonSchemaDialect.ValueNode,
lDoc.JsonSchemaDialect.Value, rDoc.JsonSchemaDialect.Value, &changes, v3.JSONSchemaDialectLabel, true) lDoc.JsonSchemaDialect.Value, rDoc.JsonSchemaDialect.Value, &changes, v3.JSONSchemaDialectLabel, true)
// tags // tags
dc.TagChanges = CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) dc.TagChanges = CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// paths // paths
if !lDoc.Paths.IsEmpty() || !rDoc.Paths.IsEmpty() { if !lDoc.Paths.IsEmpty() || !rDoc.Paths.IsEmpty() {
dc.PathsChanges = ComparePaths(lDoc.Paths.Value, rDoc.Paths.Value) dc.PathsChanges = ComparePaths(lDoc.Paths.Value, rDoc.Paths.Value)
} }
// external docs // external docs
compareDocumentExternalDocs(lDoc, rDoc, dc, &changes) compareDocumentExternalDocs(lDoc, rDoc, dc, &changes)
// info // info
compareDocumentInfo(&lDoc.Info, &rDoc.Info, dc, &changes) compareDocumentInfo(&lDoc.Info, &rDoc.Info, dc, &changes)
// security // security
if !lDoc.Security.IsEmpty() || !rDoc.Security.IsEmpty() { if !lDoc.Security.IsEmpty() || !rDoc.Security.IsEmpty() {
checkSecurity(lDoc.Security, rDoc.Security, &changes, dc) checkSecurity(lDoc.Security, rDoc.Security, &changes, dc)
} }
// compare components. // compare components.
if !lDoc.Components.IsEmpty() && !rDoc.Components.IsEmpty() { if !lDoc.Components.IsEmpty() && !rDoc.Components.IsEmpty() {
if n := CompareComponents(lDoc.Components.Value, rDoc.Components.Value); n != nil { if n := CompareComponents(lDoc.Components.Value, rDoc.Components.Value); n != nil {
dc.ComponentsChanges = n dc.ComponentsChanges = n
} }
} }
if !lDoc.Components.IsEmpty() && rDoc.Components.IsEmpty() { if !lDoc.Components.IsEmpty() && rDoc.Components.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.ComponentsLabel, CreateChange(&changes, PropertyRemoved, v3.ComponentsLabel,
lDoc.Components.ValueNode, nil, true, lDoc.Components.Value, nil) lDoc.Components.ValueNode, nil, true, lDoc.Components.Value, nil)
} }
if lDoc.Components.IsEmpty() && !rDoc.Components.IsEmpty() { if lDoc.Components.IsEmpty() && !rDoc.Components.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.ComponentsLabel, CreateChange(&changes, PropertyAdded, v3.ComponentsLabel,
rDoc.Components.ValueNode, nil, false, nil, lDoc.Components.Value) rDoc.Components.ValueNode, nil, false, nil, lDoc.Components.Value)
} }
// compare servers // compare servers
if n := checkServers(lDoc.Servers, rDoc.Servers); n != nil { if n := checkServers(lDoc.Servers, rDoc.Servers); n != nil {
dc.ServerChanges = n dc.ServerChanges = n
} }
// compare webhooks // compare webhooks
dc.WebhookChanges = CheckMapForChanges(lDoc.Webhooks.Value, rDoc.Webhooks.Value, &changes, dc.WebhookChanges = CheckMapForChanges(lDoc.Webhooks.Value, rDoc.Webhooks.Value, &changes,
v3.WebhooksLabel, ComparePathItemsV3) v3.WebhooksLabel, ComparePathItemsV3)
// extensions // extensions
dc.ExtensionChanges = CompareExtensions(lDoc.Extensions, rDoc.Extensions) dc.ExtensionChanges = CompareExtensions(lDoc.Extensions, rDoc.Extensions)
} }
CheckProperties(props) CheckProperties(props)
dc.Changes = changes dc.PropertyChanges = NewPropertyChanges(changes)
if dc.TotalChanges() <= 0 { if dc.TotalChanges() <= 0 {
return nil return nil
} }
return dc return dc
} }
func compareDocumentExternalDocs(l, r low.HasExternalDocs, dc *DocumentChanges, changes *[]*Change) { func compareDocumentExternalDocs(l, r low.HasExternalDocs, dc *DocumentChanges, changes *[]*Change) {
// external docs // external docs
if !l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() { if !l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() {
lExtDoc := l.GetExternalDocs().Value.(*base.ExternalDoc) lExtDoc := l.GetExternalDocs().Value.(*base.ExternalDoc)
rExtDoc := r.GetExternalDocs().Value.(*base.ExternalDoc) rExtDoc := r.GetExternalDocs().Value.(*base.ExternalDoc)
if !low.AreEqual(lExtDoc, rExtDoc) { if !low.AreEqual(lExtDoc, rExtDoc) {
dc.ExternalDocChanges = CompareExternalDocs(lExtDoc, rExtDoc) dc.ExternalDocChanges = CompareExternalDocs(lExtDoc, rExtDoc)
} }
} }
if l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() { if l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() {
CreateChange(changes, PropertyAdded, v3.ExternalDocsLabel, CreateChange(changes, PropertyAdded, v3.ExternalDocsLabel,
nil, r.GetExternalDocs().ValueNode, false, nil, nil, r.GetExternalDocs().ValueNode, false, nil,
r.GetExternalDocs().Value) r.GetExternalDocs().Value)
} }
if !l.GetExternalDocs().IsEmpty() && r.GetExternalDocs().IsEmpty() { if !l.GetExternalDocs().IsEmpty() && r.GetExternalDocs().IsEmpty() {
CreateChange(changes, PropertyRemoved, v3.ExternalDocsLabel, CreateChange(changes, PropertyRemoved, v3.ExternalDocsLabel,
l.GetExternalDocs().ValueNode, nil, false, l.GetExternalDocs().Value, l.GetExternalDocs().ValueNode, nil, false, l.GetExternalDocs().Value,
nil) nil)
} }
} }
func compareDocumentInfo(l, r *low.NodeReference[*base.Info], dc *DocumentChanges, changes *[]*Change) { func compareDocumentInfo(l, r *low.NodeReference[*base.Info], dc *DocumentChanges, changes *[]*Change) {
// info // info
if !l.IsEmpty() && !r.IsEmpty() { if !l.IsEmpty() && !r.IsEmpty() {
lInfo := l.Value lInfo := l.Value
rInfo := r.Value rInfo := r.Value
if !low.AreEqual(lInfo, rInfo) { if !low.AreEqual(lInfo, rInfo) {
dc.InfoChanges = CompareInfo(lInfo, rInfo) dc.InfoChanges = CompareInfo(lInfo, rInfo)
} }
} }
if l.IsEmpty() && !r.IsEmpty() { if l.IsEmpty() && !r.IsEmpty() {
CreateChange(changes, PropertyAdded, v3.InfoLabel, CreateChange(changes, PropertyAdded, v3.InfoLabel,
nil, r.ValueNode, false, nil, nil, r.ValueNode, false, nil,
r.Value) r.Value)
} }
if !l.IsEmpty() && r.IsEmpty() { if !l.IsEmpty() && r.IsEmpty() {
CreateChange(changes, PropertyRemoved, v3.InfoLabel, CreateChange(changes, PropertyRemoved, v3.InfoLabel,
l.ValueNode, nil, false, l.Value, l.ValueNode, nil, false, l.Value,
nil) nil)
} }
} }

View File

@@ -4,86 +4,86 @@
package model package model
import ( import (
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// EncodingChanges represent all the changes made to an Encoding object // EncodingChanges represent all the changes made to an Encoding object
type EncodingChanges struct { type EncodingChanges struct {
PropertyChanges *PropertyChanges
HeaderChanges map[string]*HeaderChanges `json:"headers,omitempty" yaml:"headers,omitempty"` HeaderChanges map[string]*HeaderChanges `json:"headers,omitempty" yaml:"headers,omitempty"`
} }
// TotalChanges returns the total number of changes made between two Encoding objects // TotalChanges returns the total number of changes made between two Encoding objects
func (e *EncodingChanges) TotalChanges() int { func (e *EncodingChanges) TotalChanges() int {
c := e.PropertyChanges.TotalChanges() c := e.PropertyChanges.TotalChanges()
if e.HeaderChanges != nil { if e.HeaderChanges != nil {
for i := range e.HeaderChanges { for i := range e.HeaderChanges {
c += e.HeaderChanges[i].TotalChanges() c += e.HeaderChanges[i].TotalChanges()
} }
} }
return c return c
} }
// TotalBreakingChanges returns the number of changes made between two Encoding objects that were breaking. // TotalBreakingChanges returns the number of changes made between two Encoding objects that were breaking.
func (e *EncodingChanges) TotalBreakingChanges() int { func (e *EncodingChanges) TotalBreakingChanges() int {
c := e.PropertyChanges.TotalBreakingChanges() c := e.PropertyChanges.TotalBreakingChanges()
if e.HeaderChanges != nil { if e.HeaderChanges != nil {
for i := range e.HeaderChanges { for i := range e.HeaderChanges {
c += e.HeaderChanges[i].TotalBreakingChanges() c += e.HeaderChanges[i].TotalBreakingChanges()
} }
} }
return c return c
} }
// CompareEncoding returns a pointer to *EncodingChanges that contain all changes made between a left and right // CompareEncoding returns a pointer to *EncodingChanges that contain all changes made between a left and right
// set of Encoding objects. // set of Encoding objects.
func CompareEncoding(l, r *v3.Encoding) *EncodingChanges { func CompareEncoding(l, r *v3.Encoding) *EncodingChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// ContentType // ContentType
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.ContentType.ValueNode, LeftNode: l.ContentType.ValueNode,
RightNode: r.ContentType.ValueNode, RightNode: r.ContentType.ValueNode,
Label: v3.ContentTypeLabel, Label: v3.ContentTypeLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// Explode // Explode
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Explode.ValueNode, LeftNode: l.Explode.ValueNode,
RightNode: r.Explode.ValueNode, RightNode: r.Explode.ValueNode,
Label: v3.ExplodeLabel, Label: v3.ExplodeLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// AllowReserved // AllowReserved
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.AllowReserved.ValueNode, LeftNode: l.AllowReserved.ValueNode,
RightNode: r.AllowReserved.ValueNode, RightNode: r.AllowReserved.ValueNode,
Label: v3.AllowReservedLabel, Label: v3.AllowReservedLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check everything. // check everything.
CheckProperties(props) CheckProperties(props)
ec := new(EncodingChanges) ec := new(EncodingChanges)
// headers // headers
ec.HeaderChanges = CheckMapForChanges(l.Headers.Value, r.Headers.Value, &changes, v3.HeadersLabel, CompareHeadersV3) ec.HeaderChanges = CheckMapForChanges(l.Headers.Value, r.Headers.Value, &changes, v3.HeadersLabel, CompareHeadersV3)
ec.Changes = changes ec.PropertyChanges = NewPropertyChanges(changes)
if ec.TotalChanges() <= 0 { if ec.TotalChanges() <= 0 {
return nil return nil
} }
return ec return ec
} }

View File

@@ -14,7 +14,7 @@ import (
// ExampleChanges represent changes to an Example object, part of an OpenAPI specification. // ExampleChanges represent changes to an Example object, part of an OpenAPI specification.
type ExampleChanges struct { type ExampleChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
@@ -174,7 +174,7 @@ func CompareExamples(l, r *base.Example) *ExampleChanges {
// check extensions // check extensions
ec.ExtensionChanges = CheckExtensions(l, r) ec.ExtensionChanges = CheckExtensions(l, r)
ec.Changes = changes ec.PropertyChanges = NewPropertyChanges(changes)
if ec.TotalChanges() <= 0 { if ec.TotalChanges() <= 0 {
return nil return nil
} }

View File

@@ -10,7 +10,7 @@ import (
// ExamplesChanges represents changes made between Swagger Examples objects (Not OpenAPI 3). // ExamplesChanges represents changes made between Swagger Examples objects (Not OpenAPI 3).
type ExamplesChanges struct { type ExamplesChanges struct {
PropertyChanges *PropertyChanges
} }
// TotalChanges represents the total number of changes made between Example instances. // TotalChanges represents the total number of changes made between Example instances.
@@ -73,7 +73,7 @@ func CompareExamplesV2(l, r *v2.Examples) *ExamplesChanges {
} }
ex := new(ExamplesChanges) ex := new(ExamplesChanges)
ex.Changes = changes ex.PropertyChanges = NewPropertyChanges(changes)
if ex.TotalChanges() <= 0 { if ex.TotalChanges() <= 0 {
return nil return nil
} }

View File

@@ -4,23 +4,23 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"strings" "strings"
) )
// ExtensionChanges represents any changes to custom extensions defined for an OpenAPI object. // ExtensionChanges represents any changes to custom extensions defined for an OpenAPI object.
type ExtensionChanges struct { type ExtensionChanges struct {
PropertyChanges *PropertyChanges
} }
// TotalChanges returns the total number of object extensions that were made. // TotalChanges returns the total number of object extensions that were made.
func (e *ExtensionChanges) TotalChanges() int { func (e *ExtensionChanges) TotalChanges() int {
return e.PropertyChanges.TotalChanges() return e.PropertyChanges.TotalChanges()
} }
// TotalBreakingChanges always returns 0 for Extension objects, they are non-binding. // TotalBreakingChanges always returns 0 for Extension objects, they are non-binding.
func (e *ExtensionChanges) TotalBreakingChanges() int { func (e *ExtensionChanges) TotalBreakingChanges() int {
return 0 return 0
} }
// CompareExtensions will compare a left and right map of Key/ValueReference models for any changes to // CompareExtensions will compare a left and right map of Key/ValueReference models for any changes to
@@ -31,62 +31,62 @@ func (e *ExtensionChanges) TotalBreakingChanges() int {
// there is currently no support for knowing anything changed - so it is ignored. // there is currently no support for knowing anything changed - so it is ignored.
func CompareExtensions(l, r map[low.KeyReference[string]]low.ValueReference[any]) *ExtensionChanges { func CompareExtensions(l, r map[low.KeyReference[string]]low.ValueReference[any]) *ExtensionChanges {
// look at the original and then look through the new. // look at the original and then look through the new.
seenLeft := make(map[string]*low.ValueReference[any]) seenLeft := make(map[string]*low.ValueReference[any])
seenRight := make(map[string]*low.ValueReference[any]) seenRight := make(map[string]*low.ValueReference[any])
for i := range l { for i := range l {
h := l[i] h := l[i]
seenLeft[strings.ToLower(i.Value)] = &h seenLeft[strings.ToLower(i.Value)] = &h
} }
for i := range r { for i := range r {
h := r[i] h := r[i]
seenRight[strings.ToLower(i.Value)] = &h seenRight[strings.ToLower(i.Value)] = &h
} }
var changes []*Change var changes []*Change
for i := range seenLeft { for i := range seenLeft {
CheckForObjectAdditionOrRemoval[any](seenLeft, seenRight, i, &changes, false, true) CheckForObjectAdditionOrRemoval[any](seenLeft, seenRight, i, &changes, false, true)
if seenRight[i] != nil { if seenRight[i] != nil {
var props []*PropertyCheck var props []*PropertyCheck
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: seenLeft[i].ValueNode, LeftNode: seenLeft[i].ValueNode,
RightNode: seenRight[i].ValueNode, RightNode: seenRight[i].ValueNode,
Label: i, Label: i,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: seenLeft[i].Value, Original: seenLeft[i].Value,
New: seenRight[i].Value, New: seenRight[i].Value,
}) })
// check properties // check properties
CheckProperties(props) CheckProperties(props)
} }
} }
for i := range seenRight { for i := range seenRight {
if seenLeft[i] == nil { if seenLeft[i] == nil {
CheckForObjectAdditionOrRemoval[any](seenLeft, seenRight, i, &changes, false, true) CheckForObjectAdditionOrRemoval[any](seenLeft, seenRight, i, &changes, false, true)
} }
} }
ex := new(ExtensionChanges) ex := new(ExtensionChanges)
ex.Changes = changes ex.PropertyChanges = NewPropertyChanges(changes)
if ex.TotalChanges() <= 0 { if ex.TotalChanges() <= 0 {
return nil return nil
} }
return ex return ex
} }
// CheckExtensions is a helper method to un-pack a left and right model that contains extensions. Once unpacked // CheckExtensions is a helper method to un-pack a left and right model that contains extensions. Once unpacked
// the extensions are compared and returns a pointer to ExtensionChanges. If nothing changed, nil is returned. // the extensions are compared and returns a pointer to ExtensionChanges. If nothing changed, nil is returned.
func CheckExtensions[T low.HasExtensions[T]](l, r T) *ExtensionChanges { func CheckExtensions[T low.HasExtensions[T]](l, r T) *ExtensionChanges {
var lExt, rExt map[low.KeyReference[string]]low.ValueReference[any] var lExt, rExt map[low.KeyReference[string]]low.ValueReference[any]
if len(l.GetExtensions()) > 0 { if len(l.GetExtensions()) > 0 {
lExt = l.GetExtensions() lExt = l.GetExtensions()
} }
if len(r.GetExtensions()) > 0 { if len(r.GetExtensions()) > 0 {
rExt = r.GetExtensions() rExt = r.GetExtensions()
} }
return CompareExtensions(lExt, rExt) return CompareExtensions(lExt, rExt)
} }

View File

@@ -4,69 +4,69 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// ExternalDocChanges represents changes made to any ExternalDoc object from an OpenAPI document. // ExternalDocChanges represents changes made to any ExternalDoc object from an OpenAPI document.
type ExternalDocChanges struct { type ExternalDocChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns a count of everything that changed // TotalChanges returns a count of everything that changed
func (e *ExternalDocChanges) TotalChanges() int { func (e *ExternalDocChanges) TotalChanges() int {
c := e.PropertyChanges.TotalChanges() c := e.PropertyChanges.TotalChanges()
if e.ExtensionChanges != nil { if e.ExtensionChanges != nil {
c += e.ExtensionChanges.TotalChanges() c += e.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges always returns 0 for ExternalDoc objects, they are non-binding. // TotalBreakingChanges always returns 0 for ExternalDoc objects, they are non-binding.
func (e *ExternalDocChanges) TotalBreakingChanges() int { func (e *ExternalDocChanges) TotalBreakingChanges() int {
return 0 return 0
} }
// CompareExternalDocs will compare a left (original) and a right (new) slice of ValueReference // CompareExternalDocs will compare a left (original) and a right (new) slice of ValueReference
// nodes for any changes between them. If there are changes, then a pointer to ExternalDocChanges // nodes for any changes between them. If there are changes, then a pointer to ExternalDocChanges
// is returned, otherwise if nothing changed - then nil is returned. // is returned, otherwise if nothing changed - then nil is returned.
func CompareExternalDocs(l, r *base.ExternalDoc) *ExternalDocChanges { func CompareExternalDocs(l, r *base.ExternalDoc) *ExternalDocChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// URL // URL
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.URL.ValueNode, LeftNode: l.URL.ValueNode,
RightNode: r.URL.ValueNode, RightNode: r.URL.ValueNode,
Label: v3.URLLabel, Label: v3.URLLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// description. // description.
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Description.ValueNode, LeftNode: l.Description.ValueNode,
RightNode: r.Description.ValueNode, RightNode: r.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check everything. // check everything.
CheckProperties(props) CheckProperties(props)
dc := new(ExternalDocChanges) dc := new(ExternalDocChanges)
dc.Changes = changes dc.PropertyChanges = NewPropertyChanges(changes)
// check extensions // check extensions
dc.ExtensionChanges = CheckExtensions(l, r) dc.ExtensionChanges = CheckExtensions(l, r)
if dc.TotalChanges() <= 0 { if dc.TotalChanges() <= 0 {
return nil return nil
} }
return dc return dc
} }

View File

@@ -4,255 +4,255 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
) )
// HeaderChanges represents changes made between two Header objects. Supports both Swagger and OpenAPI header // HeaderChanges represents changes made between two Header objects. Supports both Swagger and OpenAPI header
// objects, V2 only property Items is broken out into its own. // objects, V2 only property Items is broken out into its own.
type HeaderChanges struct { type HeaderChanges struct {
PropertyChanges *PropertyChanges
SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"` SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"`
ExamplesChanges map[string]*ExampleChanges `json:"examples,omitempty" yaml:"examples,omitempty"` ExamplesChanges map[string]*ExampleChanges `json:"examples,omitempty" yaml:"examples,omitempty"`
ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"` ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
// Items only supported by Swagger (V2) // Items only supported by Swagger (V2)
ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"` ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"`
} }
// TotalChanges returns the total number of changes made between two Header objects. // TotalChanges returns the total number of changes made between two Header objects.
func (h *HeaderChanges) TotalChanges() int { func (h *HeaderChanges) TotalChanges() int {
c := h.PropertyChanges.TotalChanges() c := h.PropertyChanges.TotalChanges()
for k := range h.ExamplesChanges { for k := range h.ExamplesChanges {
c += h.ExamplesChanges[k].TotalChanges() c += h.ExamplesChanges[k].TotalChanges()
} }
for k := range h.ContentChanges { for k := range h.ContentChanges {
c += h.ContentChanges[k].TotalChanges() c += h.ContentChanges[k].TotalChanges()
} }
if h.ExtensionChanges != nil { if h.ExtensionChanges != nil {
c += h.ExtensionChanges.TotalChanges() c += h.ExtensionChanges.TotalChanges()
} }
if h.SchemaChanges != nil { if h.SchemaChanges != nil {
c += h.SchemaChanges.TotalChanges() c += h.SchemaChanges.TotalChanges()
} }
if h.ItemsChanges != nil { if h.ItemsChanges != nil {
c += h.ItemsChanges.TotalChanges() c += h.ItemsChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes made between two Header instances. // TotalBreakingChanges returns the total number of breaking changes made between two Header instances.
func (h *HeaderChanges) TotalBreakingChanges() int { func (h *HeaderChanges) TotalBreakingChanges() int {
c := h.PropertyChanges.TotalBreakingChanges() c := h.PropertyChanges.TotalBreakingChanges()
for k := range h.ContentChanges { for k := range h.ContentChanges {
c += h.ContentChanges[k].TotalBreakingChanges() c += h.ContentChanges[k].TotalBreakingChanges()
} }
if h.ItemsChanges != nil { if h.ItemsChanges != nil {
c += h.ItemsChanges.TotalBreakingChanges() c += h.ItemsChanges.TotalBreakingChanges()
} }
if h.SchemaChanges != nil { if h.SchemaChanges != nil {
c += h.SchemaChanges.TotalBreakingChanges() c += h.SchemaChanges.TotalBreakingChanges()
} }
return c return c
} }
// shared header properties // shared header properties
func addOpenAPIHeaderProperties(left, right low.OpenAPIHeader, changes *[]*Change) []*PropertyCheck { func addOpenAPIHeaderProperties(left, right low.OpenAPIHeader, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
// style // style
addPropertyCheck(&props, left.GetStyle().ValueNode, right.GetStyle().ValueNode, addPropertyCheck(&props, left.GetStyle().ValueNode, right.GetStyle().ValueNode,
left.GetStyle(), right.GetStyle(), changes, v3.StyleLabel, false) left.GetStyle(), right.GetStyle(), changes, v3.StyleLabel, false)
// allow reserved // allow reserved
addPropertyCheck(&props, left.GetAllowReserved().ValueNode, right.GetAllowReserved().ValueNode, addPropertyCheck(&props, left.GetAllowReserved().ValueNode, right.GetAllowReserved().ValueNode,
left.GetAllowReserved(), right.GetAllowReserved(), changes, v3.AllowReservedLabel, false) left.GetAllowReserved(), right.GetAllowReserved(), changes, v3.AllowReservedLabel, false)
// allow empty value // allow empty value
addPropertyCheck(&props, left.GetAllowEmptyValue().ValueNode, right.GetAllowEmptyValue().ValueNode, addPropertyCheck(&props, left.GetAllowEmptyValue().ValueNode, right.GetAllowEmptyValue().ValueNode,
left.GetAllowEmptyValue(), right.GetAllowEmptyValue(), changes, v3.AllowEmptyValueLabel, true) left.GetAllowEmptyValue(), right.GetAllowEmptyValue(), changes, v3.AllowEmptyValueLabel, true)
// explode // explode
addPropertyCheck(&props, left.GetExplode().ValueNode, right.GetExplode().ValueNode, addPropertyCheck(&props, left.GetExplode().ValueNode, right.GetExplode().ValueNode,
left.GetExplode(), right.GetExplode(), changes, v3.ExplodeLabel, false) left.GetExplode(), right.GetExplode(), changes, v3.ExplodeLabel, false)
// example // example
addPropertyCheck(&props, left.GetExample().ValueNode, right.GetExample().ValueNode, addPropertyCheck(&props, left.GetExample().ValueNode, right.GetExample().ValueNode,
left.GetExample(), right.GetExample(), changes, v3.ExampleLabel, false) left.GetExample(), right.GetExample(), changes, v3.ExampleLabel, false)
// deprecated // deprecated
addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode, addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode,
left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false) left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false)
// required // required
addPropertyCheck(&props, left.GetRequired().ValueNode, right.GetRequired().ValueNode, addPropertyCheck(&props, left.GetRequired().ValueNode, right.GetRequired().ValueNode,
left.GetRequired(), right.GetRequired(), changes, v3.RequiredLabel, true) left.GetRequired(), right.GetRequired(), changes, v3.RequiredLabel, true)
return props return props
} }
// swagger only properties // swagger only properties
func addSwaggerHeaderProperties(left, right low.SwaggerHeader, changes *[]*Change) []*PropertyCheck { func addSwaggerHeaderProperties(left, right low.SwaggerHeader, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
// type // type
addPropertyCheck(&props, left.GetType().ValueNode, right.GetType().ValueNode, addPropertyCheck(&props, left.GetType().ValueNode, right.GetType().ValueNode,
left.GetType(), right.GetType(), changes, v3.TypeLabel, true) left.GetType(), right.GetType(), changes, v3.TypeLabel, true)
// format // format
addPropertyCheck(&props, left.GetFormat().ValueNode, right.GetFormat().ValueNode, addPropertyCheck(&props, left.GetFormat().ValueNode, right.GetFormat().ValueNode,
left.GetFormat(), right.GetFormat(), changes, v3.FormatLabel, true) left.GetFormat(), right.GetFormat(), changes, v3.FormatLabel, true)
// collection format // collection format
addPropertyCheck(&props, left.GetCollectionFormat().ValueNode, right.GetCollectionFormat().ValueNode, addPropertyCheck(&props, left.GetCollectionFormat().ValueNode, right.GetCollectionFormat().ValueNode,
left.GetCollectionFormat(), right.GetCollectionFormat(), changes, v3.CollectionFormatLabel, true) left.GetCollectionFormat(), right.GetCollectionFormat(), changes, v3.CollectionFormatLabel, true)
// maximum // maximum
addPropertyCheck(&props, left.GetMaximum().ValueNode, right.GetMaximum().ValueNode, addPropertyCheck(&props, left.GetMaximum().ValueNode, right.GetMaximum().ValueNode,
left.GetMaximum(), right.GetMaximum(), changes, v3.MaximumLabel, true) left.GetMaximum(), right.GetMaximum(), changes, v3.MaximumLabel, true)
// minimum // minimum
addPropertyCheck(&props, left.GetMinimum().ValueNode, right.GetMinimum().ValueNode, addPropertyCheck(&props, left.GetMinimum().ValueNode, right.GetMinimum().ValueNode,
left.GetMinimum(), right.GetMinimum(), changes, v3.MinimumLabel, true) left.GetMinimum(), right.GetMinimum(), changes, v3.MinimumLabel, true)
// exclusive maximum // exclusive maximum
addPropertyCheck(&props, left.GetExclusiveMaximum().ValueNode, right.GetExclusiveMaximum().ValueNode, addPropertyCheck(&props, left.GetExclusiveMaximum().ValueNode, right.GetExclusiveMaximum().ValueNode,
left.GetExclusiveMaximum(), right.GetExclusiveMaximum(), changes, v3.ExclusiveMaximumLabel, true) left.GetExclusiveMaximum(), right.GetExclusiveMaximum(), changes, v3.ExclusiveMaximumLabel, true)
// exclusive minimum // exclusive minimum
addPropertyCheck(&props, left.GetExclusiveMinimum().ValueNode, right.GetExclusiveMinimum().ValueNode, addPropertyCheck(&props, left.GetExclusiveMinimum().ValueNode, right.GetExclusiveMinimum().ValueNode,
left.GetExclusiveMinimum(), right.GetExclusiveMinimum(), changes, v3.ExclusiveMinimumLabel, true) left.GetExclusiveMinimum(), right.GetExclusiveMinimum(), changes, v3.ExclusiveMinimumLabel, true)
// max length // max length
addPropertyCheck(&props, left.GetMaxLength().ValueNode, right.GetMaxLength().ValueNode, addPropertyCheck(&props, left.GetMaxLength().ValueNode, right.GetMaxLength().ValueNode,
left.GetMaxLength(), right.GetMaxLength(), changes, v3.MaxLengthLabel, true) left.GetMaxLength(), right.GetMaxLength(), changes, v3.MaxLengthLabel, true)
// min length // min length
addPropertyCheck(&props, left.GetMinLength().ValueNode, right.GetMinLength().ValueNode, addPropertyCheck(&props, left.GetMinLength().ValueNode, right.GetMinLength().ValueNode,
left.GetMinLength(), right.GetMinLength(), changes, v3.MinLengthLabel, true) left.GetMinLength(), right.GetMinLength(), changes, v3.MinLengthLabel, true)
// pattern // pattern
addPropertyCheck(&props, left.GetPattern().ValueNode, right.GetPattern().ValueNode, addPropertyCheck(&props, left.GetPattern().ValueNode, right.GetPattern().ValueNode,
left.GetPattern(), right.GetPattern(), changes, v3.PatternLabel, true) left.GetPattern(), right.GetPattern(), changes, v3.PatternLabel, true)
// max items // max items
addPropertyCheck(&props, left.GetMaxItems().ValueNode, right.GetMaxItems().ValueNode, addPropertyCheck(&props, left.GetMaxItems().ValueNode, right.GetMaxItems().ValueNode,
left.GetMaxItems(), right.GetMaxItems(), changes, v3.MaxItemsLabel, true) left.GetMaxItems(), right.GetMaxItems(), changes, v3.MaxItemsLabel, true)
// min items // min items
addPropertyCheck(&props, left.GetMinItems().ValueNode, right.GetMinItems().ValueNode, addPropertyCheck(&props, left.GetMinItems().ValueNode, right.GetMinItems().ValueNode,
left.GetMinItems(), right.GetMinItems(), changes, v3.MinItemsLabel, true) left.GetMinItems(), right.GetMinItems(), changes, v3.MinItemsLabel, true)
// unique items // unique items
addPropertyCheck(&props, left.GetUniqueItems().ValueNode, right.GetUniqueItems().ValueNode, addPropertyCheck(&props, left.GetUniqueItems().ValueNode, right.GetUniqueItems().ValueNode,
left.GetUniqueItems(), right.GetUniqueItems(), changes, v3.UniqueItemsLabel, true) left.GetUniqueItems(), right.GetUniqueItems(), changes, v3.UniqueItemsLabel, true)
// multiple of // multiple of
addPropertyCheck(&props, left.GetMultipleOf().ValueNode, right.GetMultipleOf().ValueNode, addPropertyCheck(&props, left.GetMultipleOf().ValueNode, right.GetMultipleOf().ValueNode,
left.GetMultipleOf(), right.GetMultipleOf(), changes, v3.MultipleOfLabel, true) left.GetMultipleOf(), right.GetMultipleOf(), changes, v3.MultipleOfLabel, true)
return props return props
} }
// common header properties // common header properties
func addCommonHeaderProperties(left, right low.HasDescription, changes *[]*Change) []*PropertyCheck { func addCommonHeaderProperties(left, right low.HasDescription, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
// description // description
addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode, addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode,
left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false) left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false)
return props return props
} }
// CompareHeadersV2 is a Swagger compatible, typed signature used for other generic functions. It simply // CompareHeadersV2 is a Swagger compatible, typed signature used for other generic functions. It simply
// wraps CompareHeaders and provides nothing other that a typed interface. // wraps CompareHeaders and provides nothing other that a typed interface.
func CompareHeadersV2(l, r *v2.Header) *HeaderChanges { func CompareHeadersV2(l, r *v2.Header) *HeaderChanges {
return CompareHeaders(l, r) return CompareHeaders(l, r)
} }
// CompareHeadersV3 is an OpenAPI 3+ compatible, typed signature used for other generic functions. It simply // CompareHeadersV3 is an OpenAPI 3+ compatible, typed signature used for other generic functions. It simply
// wraps CompareHeaders and provides nothing other that a typed interface. // wraps CompareHeaders and provides nothing other that a typed interface.
func CompareHeadersV3(l, r *v3.Header) *HeaderChanges { func CompareHeadersV3(l, r *v3.Header) *HeaderChanges {
return CompareHeaders(l, r) return CompareHeaders(l, r)
} }
// CompareHeaders will compare left and right Header objects (any version of Swagger or OpenAPI) and return // CompareHeaders will compare left and right Header objects (any version of Swagger or OpenAPI) and return
// a pointer to HeaderChanges with anything that has changed, or nil if nothing changed. // a pointer to HeaderChanges with anything that has changed, or nil if nothing changed.
func CompareHeaders(l, r any) *HeaderChanges { func CompareHeaders(l, r any) *HeaderChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
hc := new(HeaderChanges) hc := new(HeaderChanges)
// handle swagger. // handle swagger.
if reflect.TypeOf(&v2.Header{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Header{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v2.Header{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Header{}) == reflect.TypeOf(r) {
lHeader := l.(*v2.Header) lHeader := l.(*v2.Header)
rHeader := r.(*v2.Header) rHeader := r.(*v2.Header)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lHeader, rHeader) { if low.AreEqual(lHeader, rHeader) {
return nil return nil
} }
props = append(props, addCommonHeaderProperties(lHeader, rHeader, &changes)...) props = append(props, addCommonHeaderProperties(lHeader, rHeader, &changes)...)
props = append(props, addSwaggerHeaderProperties(lHeader, rHeader, &changes)...) props = append(props, addSwaggerHeaderProperties(lHeader, rHeader, &changes)...)
// enum // enum
if len(lHeader.Enum.Value) > 0 || len(rHeader.Enum.Value) > 0 { if len(lHeader.Enum.Value) > 0 || len(rHeader.Enum.Value) > 0 {
ExtractRawValueSliceChanges(lHeader.Enum.Value, rHeader.Enum.Value, &changes, v3.EnumLabel, true) ExtractRawValueSliceChanges(lHeader.Enum.Value, rHeader.Enum.Value, &changes, v3.EnumLabel, true)
} }
// items // items
if !lHeader.Items.IsEmpty() && !rHeader.Items.IsEmpty() { if !lHeader.Items.IsEmpty() && !rHeader.Items.IsEmpty() {
if !low.AreEqual(lHeader.Items.Value, rHeader.Items.Value) { if !low.AreEqual(lHeader.Items.Value, rHeader.Items.Value) {
hc.ItemsChanges = CompareItems(lHeader.Items.Value, rHeader.Items.Value) hc.ItemsChanges = CompareItems(lHeader.Items.Value, rHeader.Items.Value)
} }
} }
if lHeader.Items.IsEmpty() && !rHeader.Items.IsEmpty() { if lHeader.Items.IsEmpty() && !rHeader.Items.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ItemsLabel, nil, CreateChange(&changes, ObjectAdded, v3.ItemsLabel, nil,
rHeader.Items.ValueNode, true, nil, rHeader.Items.Value) rHeader.Items.ValueNode, true, nil, rHeader.Items.Value)
} }
if !lHeader.Items.IsEmpty() && rHeader.Items.IsEmpty() { if !lHeader.Items.IsEmpty() && rHeader.Items.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, lHeader.Items.ValueNode, CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, lHeader.Items.ValueNode,
nil, true, lHeader.Items.Value, nil) nil, true, lHeader.Items.Value, nil)
} }
hc.ExtensionChanges = CompareExtensions(lHeader.Extensions, rHeader.Extensions) hc.ExtensionChanges = CompareExtensions(lHeader.Extensions, rHeader.Extensions)
} }
// handle OpenAPI // handle OpenAPI
if reflect.TypeOf(&v3.Header{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Header{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v3.Header{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Header{}) == reflect.TypeOf(r) {
lHeader := l.(*v3.Header) lHeader := l.(*v3.Header)
rHeader := r.(*v3.Header) rHeader := r.(*v3.Header)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lHeader, rHeader) { if low.AreEqual(lHeader, rHeader) {
return nil return nil
} }
props = append(props, addCommonHeaderProperties(lHeader, rHeader, &changes)...) props = append(props, addCommonHeaderProperties(lHeader, rHeader, &changes)...)
props = append(props, addOpenAPIHeaderProperties(lHeader, rHeader, &changes)...) props = append(props, addOpenAPIHeaderProperties(lHeader, rHeader, &changes)...)
// header // header
if !lHeader.Schema.IsEmpty() || !rHeader.Schema.IsEmpty() { if !lHeader.Schema.IsEmpty() || !rHeader.Schema.IsEmpty() {
hc.SchemaChanges = CompareSchemas(lHeader.Schema.Value, rHeader.Schema.Value) hc.SchemaChanges = CompareSchemas(lHeader.Schema.Value, rHeader.Schema.Value)
} }
// examples // examples
hc.ExamplesChanges = CheckMapForChanges(lHeader.Examples.Value, rHeader.Examples.Value, hc.ExamplesChanges = CheckMapForChanges(lHeader.Examples.Value, rHeader.Examples.Value,
&changes, v3.ExamplesLabel, CompareExamples) &changes, v3.ExamplesLabel, CompareExamples)
// content // content
hc.ContentChanges = CheckMapForChanges(lHeader.Content.Value, rHeader.Content.Value, hc.ContentChanges = CheckMapForChanges(lHeader.Content.Value, rHeader.Content.Value,
&changes, v3.ContentLabel, CompareMediaTypes) &changes, v3.ContentLabel, CompareMediaTypes)
hc.ExtensionChanges = CompareExtensions(lHeader.Extensions, rHeader.Extensions) hc.ExtensionChanges = CompareExtensions(lHeader.Extensions, rHeader.Extensions)
} }
CheckProperties(props) CheckProperties(props)
hc.Changes = changes hc.PropertyChanges = NewPropertyChanges(changes)
return hc return hc
} }

View File

@@ -4,32 +4,32 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// InfoChanges represents the number of changes to an Info object. Part of an OpenAPI document // InfoChanges represents the number of changes to an Info object. Part of an OpenAPI document
type InfoChanges struct { type InfoChanges struct {
PropertyChanges *PropertyChanges
ContactChanges *ContactChanges `json:"contact,omitempty" yaml:"contact,omitempty"` ContactChanges *ContactChanges `json:"contact,omitempty" yaml:"contact,omitempty"`
LicenseChanges *LicenseChanges `json:"license,omitempty" yaml:"license,omitempty"` LicenseChanges *LicenseChanges `json:"license,omitempty" yaml:"license,omitempty"`
} }
// TotalChanges represents the total number of changes made to an Info object. // TotalChanges represents the total number of changes made to an Info object.
func (i *InfoChanges) TotalChanges() int { func (i *InfoChanges) TotalChanges() int {
t := i.PropertyChanges.TotalChanges() t := i.PropertyChanges.TotalChanges()
if i.ContactChanges != nil { if i.ContactChanges != nil {
t += i.ContactChanges.TotalChanges() t += i.ContactChanges.TotalChanges()
} }
if i.LicenseChanges != nil { if i.LicenseChanges != nil {
t += i.LicenseChanges.TotalChanges() t += i.LicenseChanges.TotalChanges()
} }
return t return t
} }
// TotalBreakingChanges always returns 0 for Info objects, they are non-binding. // TotalBreakingChanges always returns 0 for Info objects, they are non-binding.
func (i *InfoChanges) TotalBreakingChanges() int { func (i *InfoChanges) TotalBreakingChanges() int {
return 0 return 0
} }
// CompareInfo will compare a left (original) and a right (new) Info object. Any changes // CompareInfo will compare a left (original) and a right (new) Info object. Any changes
@@ -37,88 +37,88 @@ func (i *InfoChanges) TotalBreakingChanges() int {
// returned instead. // returned instead.
func CompareInfo(l, r *base.Info) *InfoChanges { func CompareInfo(l, r *base.Info) *InfoChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// Title // Title
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Title.ValueNode, LeftNode: l.Title.ValueNode,
RightNode: r.Title.ValueNode, RightNode: r.Title.ValueNode,
Label: v3.TitleLabel, Label: v3.TitleLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// Description // Description
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Description.ValueNode, LeftNode: l.Description.ValueNode,
RightNode: r.Description.ValueNode, RightNode: r.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// TermsOfService // TermsOfService
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.TermsOfService.ValueNode, LeftNode: l.TermsOfService.ValueNode,
RightNode: r.TermsOfService.ValueNode, RightNode: r.TermsOfService.ValueNode,
Label: v3.TermsOfServiceLabel, Label: v3.TermsOfServiceLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// Version // Version
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Version.ValueNode, LeftNode: l.Version.ValueNode,
RightNode: r.Version.ValueNode, RightNode: r.Version.ValueNode,
Label: v3.VersionLabel, Label: v3.VersionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check properties // check properties
CheckProperties(props) CheckProperties(props)
i := new(InfoChanges) i := new(InfoChanges)
// compare contact. // compare contact.
if l.Contact.Value != nil && r.Contact.Value != nil { if l.Contact.Value != nil && r.Contact.Value != nil {
i.ContactChanges = CompareContact(l.Contact.Value, r.Contact.Value) i.ContactChanges = CompareContact(l.Contact.Value, r.Contact.Value)
} else { } else {
if l.Contact.Value == nil && r.Contact.Value != nil { if l.Contact.Value == nil && r.Contact.Value != nil {
CreateChange(&changes, ObjectAdded, v3.ContactLabel, CreateChange(&changes, ObjectAdded, v3.ContactLabel,
nil, r.Contact.ValueNode, false, nil, r.Contact.Value) nil, r.Contact.ValueNode, false, nil, r.Contact.Value)
} }
if l.Contact.Value != nil && r.Contact.Value == nil { if l.Contact.Value != nil && r.Contact.Value == nil {
CreateChange(&changes, ObjectRemoved, v3.ContactLabel, CreateChange(&changes, ObjectRemoved, v3.ContactLabel,
l.Contact.ValueNode, nil, false, l.Contact.Value, nil) l.Contact.ValueNode, nil, false, l.Contact.Value, nil)
} }
} }
// compare license. // compare license.
if l.License.Value != nil && r.License.Value != nil { if l.License.Value != nil && r.License.Value != nil {
i.LicenseChanges = CompareLicense(l.License.Value, r.License.Value) i.LicenseChanges = CompareLicense(l.License.Value, r.License.Value)
} else { } else {
if l.License.Value == nil && r.License.Value != nil { if l.License.Value == nil && r.License.Value != nil {
CreateChange(&changes, ObjectAdded, v3.LicenseLabel, CreateChange(&changes, ObjectAdded, v3.LicenseLabel,
nil, r.License.ValueNode, false, nil, r.License.Value) nil, r.License.ValueNode, false, nil, r.License.Value)
} }
if l.License.Value != nil && r.License.Value == nil { if l.License.Value != nil && r.License.Value == nil {
CreateChange(&changes, ObjectRemoved, v3.LicenseLabel, CreateChange(&changes, ObjectRemoved, v3.LicenseLabel,
l.License.ValueNode, nil, false, r.License.Value, nil) l.License.ValueNode, nil, false, r.License.Value, nil)
} }
} }
i.Changes = changes i.PropertyChanges = NewPropertyChanges(changes)
if i.TotalChanges() <= 0 { if i.TotalChanges() <= 0 {
return nil return nil
} }
return i return i
} }

View File

@@ -4,35 +4,35 @@
package model package model
import ( import (
v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// ItemsChanges represent changes found between a left (original) and right (modified) object. Items is only // ItemsChanges represent changes found between a left (original) and right (modified) object. Items is only
// used by Swagger documents. // used by Swagger documents.
type ItemsChanges struct { type ItemsChanges struct {
PropertyChanges *PropertyChanges
ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"` ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"`
} }
// TotalChanges returns the total number of changes found between two Items objects // TotalChanges returns the total number of changes found between two Items objects
// This is a recursive function because Items can contain Items. Be careful! // This is a recursive function because Items can contain Items. Be careful!
func (i *ItemsChanges) TotalChanges() int { func (i *ItemsChanges) TotalChanges() int {
c := i.PropertyChanges.TotalChanges() c := i.PropertyChanges.TotalChanges()
if i.ItemsChanges != nil { if i.ItemsChanges != nil {
c += i.ItemsChanges.TotalChanges() c += i.ItemsChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes found between two Swagger Items objects // TotalBreakingChanges returns the total number of breaking changes found between two Swagger Items objects
// This is a recursive method, Items are recursive, be careful! // This is a recursive method, Items are recursive, be careful!
func (i *ItemsChanges) TotalBreakingChanges() int { func (i *ItemsChanges) TotalBreakingChanges() int {
c := i.PropertyChanges.TotalBreakingChanges() c := i.PropertyChanges.TotalBreakingChanges()
if i.ItemsChanges != nil { if i.ItemsChanges != nil {
c += i.ItemsChanges.TotalBreakingChanges() c += i.ItemsChanges.TotalBreakingChanges()
} }
return c return c
} }
// CompareItems compares two sets of Swagger Item objects. If there are any changes found then a pointer to // CompareItems compares two sets of Swagger Item objects. If there are any changes found then a pointer to
@@ -42,37 +42,37 @@ func (i *ItemsChanges) TotalBreakingChanges() int {
// runaway code if not using the resolver's circular reference checking. // runaway code if not using the resolver's circular reference checking.
func CompareItems(l, r *v2.Items) *ItemsChanges { func CompareItems(l, r *v2.Items) *ItemsChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
ic := new(ItemsChanges) ic := new(ItemsChanges)
// header is identical to items, except for a description. // header is identical to items, except for a description.
props = append(props, addSwaggerHeaderProperties(l, r, &changes)...) props = append(props, addSwaggerHeaderProperties(l, r, &changes)...)
CheckProperties(props) CheckProperties(props)
if !l.Items.IsEmpty() && !r.Items.IsEmpty() { if !l.Items.IsEmpty() && !r.Items.IsEmpty() {
// inline, check hashes, if they don't match, compare. // inline, check hashes, if they don't match, compare.
if l.Items.Value.Hash() != r.Items.Value.Hash() { if l.Items.Value.Hash() != r.Items.Value.Hash() {
// compare. // compare.
ic.ItemsChanges = CompareItems(l.Items.Value, r.Items.Value) ic.ItemsChanges = CompareItems(l.Items.Value, r.Items.Value)
} }
} }
if l.Items.IsEmpty() && !r.Items.IsEmpty() { if l.Items.IsEmpty() && !r.Items.IsEmpty() {
// added items // added items
CreateChange(&changes, PropertyAdded, v3.ItemsLabel, CreateChange(&changes, PropertyAdded, v3.ItemsLabel,
nil, r.Items.GetValueNode(), true, nil, r.Items.GetValue()) nil, r.Items.GetValueNode(), true, nil, r.Items.GetValue())
} }
if !l.Items.IsEmpty() && r.Items.IsEmpty() { if !l.Items.IsEmpty() && r.Items.IsEmpty() {
// removed items // removed items
CreateChange(&changes, PropertyRemoved, v3.ItemsLabel, CreateChange(&changes, PropertyRemoved, v3.ItemsLabel,
l.Items.GetValueNode(), nil, true, l.Items.GetValue(), l.Items.GetValueNode(), nil, true, l.Items.GetValue(),
nil) nil)
} }
ic.Changes = changes ic.PropertyChanges = NewPropertyChanges(changes)
if ic.TotalChanges() <= 0 { if ic.TotalChanges() <= 0 {
return nil return nil
} }
return ic return ic
} }

View File

@@ -4,23 +4,23 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// LicenseChanges represent changes to a License object that is a child of Info object. Part of an OpenAPI document // LicenseChanges represent changes to a License object that is a child of Info object. Part of an OpenAPI document
type LicenseChanges struct { type LicenseChanges struct {
PropertyChanges *PropertyChanges
} }
// TotalChanges represents the total number of changes made to a License instance. // TotalChanges represents the total number of changes made to a License instance.
func (l *LicenseChanges) TotalChanges() int { func (l *LicenseChanges) TotalChanges() int {
return l.PropertyChanges.TotalChanges() return l.PropertyChanges.TotalChanges()
} }
// TotalBreakingChanges always returns 0 for License objects, they are non-binding. // TotalBreakingChanges always returns 0 for License objects, they are non-binding.
func (l *LicenseChanges) TotalBreakingChanges() int { func (l *LicenseChanges) TotalBreakingChanges() int {
return 0 return 0
} }
// CompareLicense will check a left (original) and right (new) License object for any changes. If there // CompareLicense will check a left (original) and right (new) License object for any changes. If there
@@ -28,38 +28,38 @@ func (l *LicenseChanges) TotalBreakingChanges() int {
// returns nil. // returns nil.
func CompareLicense(l, r *base.License) *LicenseChanges { func CompareLicense(l, r *base.License) *LicenseChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// check URL // check URL
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.URL.ValueNode, LeftNode: l.URL.ValueNode,
RightNode: r.URL.ValueNode, RightNode: r.URL.ValueNode,
Label: v3.URLLabel, Label: v3.URLLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check name // check name
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Name.ValueNode, LeftNode: l.Name.ValueNode,
RightNode: r.Name.ValueNode, RightNode: r.Name.ValueNode,
Label: v3.NameLabel, Label: v3.NameLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check everything. // check everything.
CheckProperties(props) CheckProperties(props)
lc := new(LicenseChanges) lc := new(LicenseChanges)
lc.Changes = changes lc.PropertyChanges = NewPropertyChanges(changes)
if lc.TotalChanges() <= 0 { if lc.TotalChanges() <= 0 {
return nil return nil
} }
return lc return lc
} }

View File

@@ -4,144 +4,144 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// LinkChanges represent changes made between two OpenAPI Link Objects. // LinkChanges represent changes made between two OpenAPI Link Objects.
type LinkChanges struct { type LinkChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
ServerChanges *ServerChanges `json:"server,omitempty" yaml:"server,omitempty"` ServerChanges *ServerChanges `json:"server,omitempty" yaml:"server,omitempty"`
} }
// TotalChanges returns the total changes made between OpenAPI Link objects // TotalChanges returns the total changes made between OpenAPI Link objects
func (l *LinkChanges) TotalChanges() int { func (l *LinkChanges) TotalChanges() int {
c := l.PropertyChanges.TotalChanges() c := l.PropertyChanges.TotalChanges()
if l.ExtensionChanges != nil { if l.ExtensionChanges != nil {
c += l.ExtensionChanges.TotalChanges() c += l.ExtensionChanges.TotalChanges()
} }
if l.ServerChanges != nil { if l.ServerChanges != nil {
c += l.ServerChanges.TotalChanges() c += l.ServerChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the number of breaking changes made between two OpenAPI Link Objects // TotalBreakingChanges returns the number of breaking changes made between two OpenAPI Link Objects
func (l *LinkChanges) TotalBreakingChanges() int { func (l *LinkChanges) TotalBreakingChanges() int {
c := l.PropertyChanges.TotalBreakingChanges() c := l.PropertyChanges.TotalBreakingChanges()
if l.ServerChanges != nil { if l.ServerChanges != nil {
c += l.ServerChanges.TotalBreakingChanges() c += l.ServerChanges.TotalBreakingChanges()
} }
return c return c
} }
// CompareLinks checks a left and right OpenAPI Link for any changes. If they are found, returns a pointer to // CompareLinks checks a left and right OpenAPI Link for any changes. If they are found, returns a pointer to
// LinkChanges, and returns nil if nothing is found. // LinkChanges, and returns nil if nothing is found.
func CompareLinks(l, r *v3.Link) *LinkChanges { func CompareLinks(l, r *v3.Link) *LinkChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
var props []*PropertyCheck var props []*PropertyCheck
var changes []*Change var changes []*Change
// operation ref // operation ref
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.OperationRef.ValueNode, LeftNode: l.OperationRef.ValueNode,
RightNode: r.OperationRef.ValueNode, RightNode: r.OperationRef.ValueNode,
Label: v3.OperationRefLabel, Label: v3.OperationRefLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// operation id // operation id
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.OperationId.ValueNode, LeftNode: l.OperationId.ValueNode,
RightNode: r.OperationId.ValueNode, RightNode: r.OperationId.ValueNode,
Label: v3.OperationIdLabel, Label: v3.OperationIdLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// request body // request body
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.RequestBody.ValueNode, LeftNode: l.RequestBody.ValueNode,
RightNode: r.RequestBody.ValueNode, RightNode: r.RequestBody.ValueNode,
Label: v3.RequestBodyLabel, Label: v3.RequestBodyLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// description // description
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Description.ValueNode, LeftNode: l.Description.ValueNode,
RightNode: r.Description.ValueNode, RightNode: r.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
CheckProperties(props) CheckProperties(props)
lc := new(LinkChanges) lc := new(LinkChanges)
lc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) lc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
// server // server
if !l.Server.IsEmpty() && !r.Server.IsEmpty() { if !l.Server.IsEmpty() && !r.Server.IsEmpty() {
if !low.AreEqual(l.Server.Value, r.Server.Value) { if !low.AreEqual(l.Server.Value, r.Server.Value) {
lc.ServerChanges = CompareServers(l.Server.Value, r.Server.Value) lc.ServerChanges = CompareServers(l.Server.Value, r.Server.Value)
} }
} }
if !l.Server.IsEmpty() && r.Server.IsEmpty() { if !l.Server.IsEmpty() && r.Server.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.ServerLabel, CreateChange(&changes, PropertyRemoved, v3.ServerLabel,
l.Server.ValueNode, nil, true, l.Server.ValueNode, nil, true,
l.Server.Value, nil) l.Server.Value, nil)
} }
if l.Server.IsEmpty() && !r.Server.IsEmpty() { if l.Server.IsEmpty() && !r.Server.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.ServerLabel, CreateChange(&changes, PropertyAdded, v3.ServerLabel,
nil, r.Server.ValueNode, true, nil, r.Server.ValueNode, true,
nil, r.Server.Value) nil, r.Server.Value)
} }
// parameters // parameters
lValues := make(map[string]low.ValueReference[string]) lValues := make(map[string]low.ValueReference[string])
rValues := make(map[string]low.ValueReference[string]) rValues := make(map[string]low.ValueReference[string])
for i := range l.Parameters { for i := range l.Parameters {
lValues[i.Value] = l.Parameters[i] lValues[i.Value] = l.Parameters[i]
} }
for i := range r.Parameters { for i := range r.Parameters {
rValues[i.Value] = r.Parameters[i] rValues[i.Value] = r.Parameters[i]
} }
for k := range lValues { for k := range lValues {
if _, ok := rValues[k]; !ok { if _, ok := rValues[k]; !ok {
CreateChange(&changes, ObjectRemoved, v3.ParametersLabel, CreateChange(&changes, ObjectRemoved, v3.ParametersLabel,
lValues[k].ValueNode, nil, true, lValues[k].ValueNode, nil, true,
k, nil) k, nil)
continue continue
} }
if lValues[k].Value != rValues[k].Value { if lValues[k].Value != rValues[k].Value {
CreateChange(&changes, Modified, v3.ParametersLabel, CreateChange(&changes, Modified, v3.ParametersLabel,
lValues[k].ValueNode, rValues[k].ValueNode, true, lValues[k].ValueNode, rValues[k].ValueNode, true,
k, k) k, k)
} }
} }
for k := range rValues { for k := range rValues {
if _, ok := lValues[k]; !ok { if _, ok := lValues[k]; !ok {
CreateChange(&changes, ObjectAdded, v3.ParametersLabel, CreateChange(&changes, ObjectAdded, v3.ParametersLabel,
nil, rValues[k].ValueNode, true, nil, rValues[k].ValueNode, true,
nil, k) nil, k)
} }
} }
lc.Changes = changes lc.PropertyChanges = NewPropertyChanges(changes)
return lc return lc
} }

View File

@@ -13,7 +13,7 @@ import (
// MediaTypeChanges represent changes made between two OpenAPI MediaType instances. // MediaTypeChanges represent changes made between two OpenAPI MediaType instances.
type MediaTypeChanges struct { type MediaTypeChanges struct {
PropertyChanges *PropertyChanges
SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"` SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
ExampleChanges map[string]*ExampleChanges `json:"examples,omitempty" yaml:"examples,omitempty"` ExampleChanges map[string]*ExampleChanges `json:"examples,omitempty" yaml:"examples,omitempty"`
@@ -130,7 +130,7 @@ func CompareMediaTypes(l, r *v3.MediaType) *MediaTypeChanges {
&changes, v3.EncodingLabel, CompareEncoding) &changes, v3.EncodingLabel, CompareEncoding)
mc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) mc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
mc.Changes = changes mc.PropertyChanges = NewPropertyChanges(changes)
if mc.TotalChanges() <= 0 { if mc.TotalChanges() <= 0 {
return nil return nil

View File

@@ -4,222 +4,222 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// OAuthFlowsChanges represents changes found between two OpenAPI OAuthFlows objects. // OAuthFlowsChanges represents changes found between two OpenAPI OAuthFlows objects.
type OAuthFlowsChanges struct { type OAuthFlowsChanges struct {
PropertyChanges *PropertyChanges
ImplicitChanges *OAuthFlowChanges `json:"implicit,omitempty" yaml:"implicit,omitempty"` ImplicitChanges *OAuthFlowChanges `json:"implicit,omitempty" yaml:"implicit,omitempty"`
PasswordChanges *OAuthFlowChanges `json:"password,omitempty" yaml:"password,omitempty"` PasswordChanges *OAuthFlowChanges `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentialsChanges *OAuthFlowChanges `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"` ClientCredentialsChanges *OAuthFlowChanges `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
AuthorizationCodeChanges *OAuthFlowChanges `json:"authCode,omitempty" yaml:"authCode,omitempty"` AuthorizationCodeChanges *OAuthFlowChanges `json:"authCode,omitempty" yaml:"authCode,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns the number of changes made between two OAuthFlows instances. // TotalChanges returns the number of changes made between two OAuthFlows instances.
func (o *OAuthFlowsChanges) TotalChanges() int { func (o *OAuthFlowsChanges) TotalChanges() int {
c := o.PropertyChanges.TotalChanges() c := o.PropertyChanges.TotalChanges()
if o.ImplicitChanges != nil { if o.ImplicitChanges != nil {
c += o.ImplicitChanges.TotalChanges() c += o.ImplicitChanges.TotalChanges()
} }
if o.PasswordChanges != nil { if o.PasswordChanges != nil {
c += o.PasswordChanges.TotalChanges() c += o.PasswordChanges.TotalChanges()
} }
if o.ClientCredentialsChanges != nil { if o.ClientCredentialsChanges != nil {
c += o.ClientCredentialsChanges.TotalChanges() c += o.ClientCredentialsChanges.TotalChanges()
} }
if o.AuthorizationCodeChanges != nil { if o.AuthorizationCodeChanges != nil {
c += o.AuthorizationCodeChanges.TotalChanges() c += o.AuthorizationCodeChanges.TotalChanges()
} }
if o.ExtensionChanges != nil { if o.ExtensionChanges != nil {
c += o.ExtensionChanges.TotalChanges() c += o.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the number of breaking changes made between two OAuthFlows objects. // TotalBreakingChanges returns the number of breaking changes made between two OAuthFlows objects.
func (o *OAuthFlowsChanges) TotalBreakingChanges() int { func (o *OAuthFlowsChanges) TotalBreakingChanges() int {
c := o.PropertyChanges.TotalBreakingChanges() c := o.PropertyChanges.TotalBreakingChanges()
if o.ImplicitChanges != nil { if o.ImplicitChanges != nil {
c += o.ImplicitChanges.TotalBreakingChanges() c += o.ImplicitChanges.TotalBreakingChanges()
} }
if o.PasswordChanges != nil { if o.PasswordChanges != nil {
c += o.PasswordChanges.TotalBreakingChanges() c += o.PasswordChanges.TotalBreakingChanges()
} }
if o.ClientCredentialsChanges != nil { if o.ClientCredentialsChanges != nil {
c += o.ClientCredentialsChanges.TotalBreakingChanges() c += o.ClientCredentialsChanges.TotalBreakingChanges()
} }
if o.AuthorizationCodeChanges != nil { if o.AuthorizationCodeChanges != nil {
c += o.AuthorizationCodeChanges.TotalBreakingChanges() c += o.AuthorizationCodeChanges.TotalBreakingChanges()
} }
return c return c
} }
// CompareOAuthFlows compares a left and right OAuthFlows object. If changes are found a pointer to *OAuthFlowsChanges // CompareOAuthFlows compares a left and right OAuthFlows object. If changes are found a pointer to *OAuthFlowsChanges
// is returned, otherwise nil is returned. // is returned, otherwise nil is returned.
func CompareOAuthFlows(l, r *v3.OAuthFlows) *OAuthFlowsChanges { func CompareOAuthFlows(l, r *v3.OAuthFlows) *OAuthFlowsChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
oa := new(OAuthFlowsChanges) oa := new(OAuthFlowsChanges)
var changes []*Change var changes []*Change
// client credentials // client credentials
if !l.ClientCredentials.IsEmpty() && !r.ClientCredentials.IsEmpty() { if !l.ClientCredentials.IsEmpty() && !r.ClientCredentials.IsEmpty() {
oa.ClientCredentialsChanges = CompareOAuthFlow(l.ClientCredentials.Value, r.ClientCredentials.Value) oa.ClientCredentialsChanges = CompareOAuthFlow(l.ClientCredentials.Value, r.ClientCredentials.Value)
} }
if !l.ClientCredentials.IsEmpty() && r.ClientCredentials.IsEmpty() { if !l.ClientCredentials.IsEmpty() && r.ClientCredentials.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.ClientCredentialsLabel, CreateChange(&changes, ObjectRemoved, v3.ClientCredentialsLabel,
l.ClientCredentials.ValueNode, nil, true, l.ClientCredentials.ValueNode, nil, true,
l.ClientCredentials.Value, nil) l.ClientCredentials.Value, nil)
} }
if l.ClientCredentials.IsEmpty() && !r.ClientCredentials.IsEmpty() { if l.ClientCredentials.IsEmpty() && !r.ClientCredentials.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ClientCredentialsLabel, CreateChange(&changes, ObjectAdded, v3.ClientCredentialsLabel,
nil, r.ClientCredentials.ValueNode, false, nil, r.ClientCredentials.ValueNode, false,
nil, r.ClientCredentials.Value) nil, r.ClientCredentials.Value)
} }
// implicit // implicit
if !l.Implicit.IsEmpty() && !r.Implicit.IsEmpty() { if !l.Implicit.IsEmpty() && !r.Implicit.IsEmpty() {
oa.ImplicitChanges = CompareOAuthFlow(l.Implicit.Value, r.Implicit.Value) oa.ImplicitChanges = CompareOAuthFlow(l.Implicit.Value, r.Implicit.Value)
} }
if !l.Implicit.IsEmpty() && r.Implicit.IsEmpty() { if !l.Implicit.IsEmpty() && r.Implicit.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.ImplicitLabel, CreateChange(&changes, ObjectRemoved, v3.ImplicitLabel,
l.Implicit.ValueNode, nil, true, l.Implicit.ValueNode, nil, true,
l.Implicit.Value, nil) l.Implicit.Value, nil)
} }
if l.Implicit.IsEmpty() && !r.Implicit.IsEmpty() { if l.Implicit.IsEmpty() && !r.Implicit.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ImplicitLabel, CreateChange(&changes, ObjectAdded, v3.ImplicitLabel,
nil, r.Implicit.ValueNode, false, nil, r.Implicit.ValueNode, false,
nil, r.Implicit.Value) nil, r.Implicit.Value)
} }
// password // password
if !l.Password.IsEmpty() && !r.Password.IsEmpty() { if !l.Password.IsEmpty() && !r.Password.IsEmpty() {
oa.PasswordChanges = CompareOAuthFlow(l.Password.Value, r.Password.Value) oa.PasswordChanges = CompareOAuthFlow(l.Password.Value, r.Password.Value)
} }
if !l.Password.IsEmpty() && r.Password.IsEmpty() { if !l.Password.IsEmpty() && r.Password.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.PasswordLabel, CreateChange(&changes, ObjectRemoved, v3.PasswordLabel,
l.Password.ValueNode, nil, true, l.Password.ValueNode, nil, true,
l.Password.Value, nil) l.Password.Value, nil)
} }
if l.Password.IsEmpty() && !r.Password.IsEmpty() { if l.Password.IsEmpty() && !r.Password.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.PasswordLabel, CreateChange(&changes, ObjectAdded, v3.PasswordLabel,
nil, r.Password.ValueNode, false, nil, r.Password.ValueNode, false,
nil, r.Password.Value) nil, r.Password.Value)
} }
// auth code // auth code
if !l.AuthorizationCode.IsEmpty() && !r.AuthorizationCode.IsEmpty() { if !l.AuthorizationCode.IsEmpty() && !r.AuthorizationCode.IsEmpty() {
oa.AuthorizationCodeChanges = CompareOAuthFlow(l.AuthorizationCode.Value, r.AuthorizationCode.Value) oa.AuthorizationCodeChanges = CompareOAuthFlow(l.AuthorizationCode.Value, r.AuthorizationCode.Value)
} }
if !l.AuthorizationCode.IsEmpty() && r.AuthorizationCode.IsEmpty() { if !l.AuthorizationCode.IsEmpty() && r.AuthorizationCode.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.AuthorizationCodeLabel, CreateChange(&changes, ObjectRemoved, v3.AuthorizationCodeLabel,
l.AuthorizationCode.ValueNode, nil, true, l.AuthorizationCode.ValueNode, nil, true,
l.AuthorizationCode.Value, nil) l.AuthorizationCode.Value, nil)
} }
if l.AuthorizationCode.IsEmpty() && !r.AuthorizationCode.IsEmpty() { if l.AuthorizationCode.IsEmpty() && !r.AuthorizationCode.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.AuthorizationCodeLabel, CreateChange(&changes, ObjectAdded, v3.AuthorizationCodeLabel,
nil, r.AuthorizationCode.ValueNode, false, nil, r.AuthorizationCode.ValueNode, false,
nil, r.AuthorizationCode.Value) nil, r.AuthorizationCode.Value)
} }
oa.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) oa.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
oa.Changes = changes oa.PropertyChanges = NewPropertyChanges(changes)
return oa return oa
} }
// OAuthFlowChanges represents an OpenAPI OAuthFlow object. // OAuthFlowChanges represents an OpenAPI OAuthFlow object.
type OAuthFlowChanges struct { type OAuthFlowChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns the total number of changes made between two OAuthFlow objects // TotalChanges returns the total number of changes made between two OAuthFlow objects
func (o *OAuthFlowChanges) TotalChanges() int { func (o *OAuthFlowChanges) TotalChanges() int {
c := o.PropertyChanges.TotalChanges() c := o.PropertyChanges.TotalChanges()
if o.ExtensionChanges != nil { if o.ExtensionChanges != nil {
c += o.ExtensionChanges.TotalChanges() c += o.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes made between two OAuthFlow objects // TotalBreakingChanges returns the total number of breaking changes made between two OAuthFlow objects
func (o *OAuthFlowChanges) TotalBreakingChanges() int { func (o *OAuthFlowChanges) TotalBreakingChanges() int {
return o.PropertyChanges.TotalBreakingChanges() return o.PropertyChanges.TotalBreakingChanges()
} }
// CompareOAuthFlow checks a left and a right OAuthFlow object for changes. If found, returns a pointer to // CompareOAuthFlow checks a left and a right OAuthFlow object for changes. If found, returns a pointer to
// an OAuthFlowChanges instance, or nil if nothing is found. // an OAuthFlowChanges instance, or nil if nothing is found.
func CompareOAuthFlow(l, r *v3.OAuthFlow) *OAuthFlowChanges { func CompareOAuthFlow(l, r *v3.OAuthFlow) *OAuthFlowChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// authorization url // authorization url
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.AuthorizationUrl.ValueNode, LeftNode: l.AuthorizationUrl.ValueNode,
RightNode: r.AuthorizationUrl.ValueNode, RightNode: r.AuthorizationUrl.ValueNode,
Label: v3.AuthorizationUrlLabel, Label: v3.AuthorizationUrlLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// token url // token url
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.TokenUrl.ValueNode, LeftNode: l.TokenUrl.ValueNode,
RightNode: r.TokenUrl.ValueNode, RightNode: r.TokenUrl.ValueNode,
Label: v3.TokenUrlLabel, Label: v3.TokenUrlLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// refresh url // refresh url
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.RefreshUrl.ValueNode, LeftNode: l.RefreshUrl.ValueNode,
RightNode: r.RefreshUrl.ValueNode, RightNode: r.RefreshUrl.ValueNode,
Label: v3.RefreshUrlLabel, Label: v3.RefreshUrlLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
CheckProperties(props) CheckProperties(props)
for v := range l.Scopes.Value { for v := range l.Scopes.Value {
if r != nil && r.FindScope(v.Value) == nil { if r != nil && r.FindScope(v.Value) == nil {
CreateChange(&changes, ObjectRemoved, v3.Scopes, CreateChange(&changes, ObjectRemoved, v3.Scopes,
l.Scopes.Value[v].ValueNode, nil, true, l.Scopes.Value[v].ValueNode, nil, true,
v.Value, nil) v.Value, nil)
continue continue
} }
if r != nil && r.FindScope(v.Value) != nil { if r != nil && r.FindScope(v.Value) != nil {
if l.Scopes.Value[v].Value != r.FindScope(v.Value).Value { if l.Scopes.Value[v].Value != r.FindScope(v.Value).Value {
CreateChange(&changes, Modified, v3.Scopes, CreateChange(&changes, Modified, v3.Scopes,
l.Scopes.Value[v].ValueNode, r.FindScope(v.Value).ValueNode, true, l.Scopes.Value[v].ValueNode, r.FindScope(v.Value).ValueNode, true,
l.Scopes.Value[v].Value, r.FindScope(v.Value).Value) l.Scopes.Value[v].Value, r.FindScope(v.Value).Value)
} }
} }
} }
for v := range r.Scopes.Value { for v := range r.Scopes.Value {
if l != nil && l.FindScope(v.Value) == nil { if l != nil && l.FindScope(v.Value) == nil {
CreateChange(&changes, ObjectAdded, v3.Scopes, CreateChange(&changes, ObjectAdded, v3.Scopes,
nil, r.Scopes.Value[v].ValueNode, false, nil, r.Scopes.Value[v].ValueNode, false,
nil, v.Value) nil, v.Value)
} }
} }
oa := new(OAuthFlowChanges) oa := new(OAuthFlowChanges)
oa.Changes = changes oa.PropertyChanges = NewPropertyChanges(changes)
oa.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) oa.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
return oa return oa
} }

View File

@@ -4,155 +4,154 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
) )
// OperationChanges represent changes made between two Swagger or OpenAPI Operation objects. // OperationChanges represent changes made between two Swagger or OpenAPI Operation objects.
type OperationChanges struct { type OperationChanges struct {
PropertyChanges *PropertyChanges
ExternalDocChanges *ExternalDocChanges `json:"externalDoc,omitempty" yaml:"externalDoc,omitempty"` ExternalDocChanges *ExternalDocChanges `json:"externalDoc,omitempty" yaml:"externalDoc,omitempty"`
ParameterChanges []*ParameterChanges `json:"parameters,omitempty" yaml:"parameters,omitempty"` ParameterChanges []*ParameterChanges `json:"parameters,omitempty" yaml:"parameters,omitempty"`
ResponsesChanges *ResponsesChanges `json:"responses,omitempty" yaml:"responses,omitempty"` ResponsesChanges *ResponsesChanges `json:"responses,omitempty" yaml:"responses,omitempty"`
SecurityRequirementChanges []*SecurityRequirementChanges `json:"securityRequirements,omitempty" yaml:"securityRequirements,omitempty"` SecurityRequirementChanges []*SecurityRequirementChanges `json:"securityRequirements,omitempty" yaml:"securityRequirements,omitempty"`
// OpenAPI 3+ only changes // OpenAPI 3+ only changes
RequestBodyChanges *RequestBodyChanges `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` RequestBodyChanges *RequestBodyChanges `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
ServerChanges []*ServerChanges `json:"servers,omitempty" yaml:"servers,omitempty"` ServerChanges []*ServerChanges `json:"servers,omitempty" yaml:"servers,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
CallbackChanges map[string]*CallbackChanges `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` CallbackChanges map[string]*CallbackChanges `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
} }
// TotalChanges returns the total number of changes made between two Swagger or OpenAPI Operation objects. // TotalChanges returns the total number of changes made between two Swagger or OpenAPI Operation objects.
func (o *OperationChanges) TotalChanges() int { func (o *OperationChanges) TotalChanges() int {
c := o.PropertyChanges.TotalChanges() c := o.PropertyChanges.TotalChanges()
if o.ExternalDocChanges != nil { if o.ExternalDocChanges != nil {
c += o.ExternalDocChanges.TotalChanges() c += o.ExternalDocChanges.TotalChanges()
} }
for k := range o.ParameterChanges { for k := range o.ParameterChanges {
c += o.ParameterChanges[k].TotalChanges() c += o.ParameterChanges[k].TotalChanges()
} }
if o.ResponsesChanges != nil { if o.ResponsesChanges != nil {
c += o.ResponsesChanges.TotalChanges() c += o.ResponsesChanges.TotalChanges()
} }
for k := range o.SecurityRequirementChanges { for k := range o.SecurityRequirementChanges {
c += o.SecurityRequirementChanges[k].TotalChanges() c += o.SecurityRequirementChanges[k].TotalChanges()
} }
if o.RequestBodyChanges != nil { if o.RequestBodyChanges != nil {
c += o.RequestBodyChanges.TotalChanges() c += o.RequestBodyChanges.TotalChanges()
} }
for k := range o.ServerChanges { for k := range o.ServerChanges {
c += o.ServerChanges[k].TotalChanges() c += o.ServerChanges[k].TotalChanges()
} }
for k := range o.CallbackChanges { for k := range o.CallbackChanges {
c += o.CallbackChanges[k].TotalChanges() c += o.CallbackChanges[k].TotalChanges()
} }
if o.ExtensionChanges != nil { if o.ExtensionChanges != nil {
c += o.ExtensionChanges.TotalChanges() c += o.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes made between two Swagger // TotalBreakingChanges returns the total number of breaking changes made between two Swagger
// or OpenAPI Operation objects. // or OpenAPI Operation objects.
func (o *OperationChanges) TotalBreakingChanges() int { func (o *OperationChanges) TotalBreakingChanges() int {
c := o.PropertyChanges.TotalBreakingChanges() c := o.PropertyChanges.TotalBreakingChanges()
if o.ExternalDocChanges != nil { if o.ExternalDocChanges != nil {
c += o.ExternalDocChanges.TotalBreakingChanges() c += o.ExternalDocChanges.TotalBreakingChanges()
} }
for k := range o.ParameterChanges { for k := range o.ParameterChanges {
c += o.ParameterChanges[k].TotalBreakingChanges() c += o.ParameterChanges[k].TotalBreakingChanges()
} }
if o.ResponsesChanges != nil { if o.ResponsesChanges != nil {
c += o.ResponsesChanges.TotalBreakingChanges() c += o.ResponsesChanges.TotalBreakingChanges()
} }
for k := range o.SecurityRequirementChanges { for k := range o.SecurityRequirementChanges {
c += o.SecurityRequirementChanges[k].TotalBreakingChanges() c += o.SecurityRequirementChanges[k].TotalBreakingChanges()
} }
for k := range o.CallbackChanges { for k := range o.CallbackChanges {
c += o.CallbackChanges[k].TotalBreakingChanges() c += o.CallbackChanges[k].TotalBreakingChanges()
} }
if o.RequestBodyChanges != nil { if o.RequestBodyChanges != nil {
c += o.RequestBodyChanges.TotalBreakingChanges() c += o.RequestBodyChanges.TotalBreakingChanges()
} }
for k := range o.ServerChanges { for k := range o.ServerChanges {
c += o.ServerChanges[k].TotalBreakingChanges() c += o.ServerChanges[k].TotalBreakingChanges()
} }
// todo: add callbacks in here. return c
return c
} }
// check for properties shared between operations objects. // check for properties shared between operations objects.
func addSharedOperationProperties(left, right low.SharedOperations, changes *[]*Change) []*PropertyCheck { func addSharedOperationProperties(left, right low.SharedOperations, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
// tags // tags
if len(left.GetTags().Value) > 0 || len(right.GetTags().Value) > 0 { if len(left.GetTags().Value) > 0 || len(right.GetTags().Value) > 0 {
ExtractStringValueSliceChanges(left.GetTags().Value, right.GetTags().Value, ExtractStringValueSliceChanges(left.GetTags().Value, right.GetTags().Value,
changes, v3.TagsLabel, false) changes, v3.TagsLabel, false)
} }
// summary // summary
addPropertyCheck(&props, left.GetSummary().ValueNode, right.GetSummary().ValueNode, addPropertyCheck(&props, left.GetSummary().ValueNode, right.GetSummary().ValueNode,
left.GetSummary(), right.GetSummary(), changes, v3.SummaryLabel, false) left.GetSummary(), right.GetSummary(), changes, v3.SummaryLabel, false)
// description // description
addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode, addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode,
left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false) left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false)
// deprecated // deprecated
addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode, addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode,
left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false) left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false)
// operation id // operation id
addPropertyCheck(&props, left.GetOperationId().ValueNode, right.GetOperationId().ValueNode, addPropertyCheck(&props, left.GetOperationId().ValueNode, right.GetOperationId().ValueNode,
left.GetOperationId(), right.GetOperationId(), changes, v3.OperationIdLabel, true) left.GetOperationId(), right.GetOperationId(), changes, v3.OperationIdLabel, true)
return props return props
} }
// check shared objects // check shared objects
func compareSharedOperationObjects(l, r low.SharedOperations, changes *[]*Change, opChanges *OperationChanges) { func compareSharedOperationObjects(l, r low.SharedOperations, changes *[]*Change, opChanges *OperationChanges) {
// external docs // external docs
if !l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() { if !l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() {
lExtDoc := l.GetExternalDocs().Value.(*base.ExternalDoc) lExtDoc := l.GetExternalDocs().Value.(*base.ExternalDoc)
rExtDoc := r.GetExternalDocs().Value.(*base.ExternalDoc) rExtDoc := r.GetExternalDocs().Value.(*base.ExternalDoc)
if !low.AreEqual(lExtDoc, rExtDoc) { if !low.AreEqual(lExtDoc, rExtDoc) {
opChanges.ExternalDocChanges = CompareExternalDocs(lExtDoc, rExtDoc) opChanges.ExternalDocChanges = CompareExternalDocs(lExtDoc, rExtDoc)
} }
} }
if l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() { if l.GetExternalDocs().IsEmpty() && !r.GetExternalDocs().IsEmpty() {
CreateChange(changes, PropertyAdded, v3.ExternalDocsLabel, CreateChange(changes, PropertyAdded, v3.ExternalDocsLabel,
nil, r.GetExternalDocs().ValueNode, false, nil, nil, r.GetExternalDocs().ValueNode, false, nil,
r.GetExternalDocs().Value) r.GetExternalDocs().Value)
} }
if !l.GetExternalDocs().IsEmpty() && r.GetExternalDocs().IsEmpty() { if !l.GetExternalDocs().IsEmpty() && r.GetExternalDocs().IsEmpty() {
CreateChange(changes, PropertyRemoved, v3.ExternalDocsLabel, CreateChange(changes, PropertyRemoved, v3.ExternalDocsLabel,
l.GetExternalDocs().ValueNode, nil, false, l.GetExternalDocs().Value, l.GetExternalDocs().ValueNode, nil, false, l.GetExternalDocs().Value,
nil) nil)
} }
// responses // responses
if !l.GetResponses().IsEmpty() && !r.GetResponses().IsEmpty() { if !l.GetResponses().IsEmpty() && !r.GetResponses().IsEmpty() {
opChanges.ResponsesChanges = CompareResponses(l.GetResponses().Value, r.GetResponses().Value) opChanges.ResponsesChanges = CompareResponses(l.GetResponses().Value, r.GetResponses().Value)
} }
if l.GetResponses().IsEmpty() && !r.GetResponses().IsEmpty() { if l.GetResponses().IsEmpty() && !r.GetResponses().IsEmpty() {
CreateChange(changes, PropertyAdded, v3.ResponsesLabel, CreateChange(changes, PropertyAdded, v3.ResponsesLabel,
nil, r.GetResponses().ValueNode, false, nil, nil, r.GetResponses().ValueNode, false, nil,
r.GetResponses().Value) r.GetResponses().Value)
} }
if !l.GetResponses().IsEmpty() && r.GetResponses().IsEmpty() { if !l.GetResponses().IsEmpty() && r.GetResponses().IsEmpty() {
CreateChange(changes, PropertyRemoved, v3.ResponsesLabel, CreateChange(changes, PropertyRemoved, v3.ResponsesLabel,
l.GetResponses().ValueNode, nil, true, l.GetResponses().Value, l.GetResponses().ValueNode, nil, true, l.GetResponses().Value,
nil) nil)
} }
} }
@@ -160,371 +159,371 @@ func compareSharedOperationObjects(l, r low.SharedOperations, changes *[]*Change
// a pointer to an OperationChanges instance, or nil if nothing is found. // a pointer to an OperationChanges instance, or nil if nothing is found.
func CompareOperations(l, r any) *OperationChanges { func CompareOperations(l, r any) *OperationChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
oc := new(OperationChanges) oc := new(OperationChanges)
// Swagger // Swagger
if reflect.TypeOf(&v2.Operation{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.Operation{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.Operation{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.Operation{}) == reflect.TypeOf(r) {
lOperation := l.(*v2.Operation) lOperation := l.(*v2.Operation)
rOperation := r.(*v2.Operation) rOperation := r.(*v2.Operation)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lOperation, rOperation) { if low.AreEqual(lOperation, rOperation) {
return nil return nil
} }
props = append(props, addSharedOperationProperties(lOperation, rOperation, &changes)...) props = append(props, addSharedOperationProperties(lOperation, rOperation, &changes)...)
compareSharedOperationObjects(lOperation, rOperation, &changes, oc) compareSharedOperationObjects(lOperation, rOperation, &changes, oc)
// parameters // parameters
lParamsUntyped := lOperation.GetParameters() lParamsUntyped := lOperation.GetParameters()
rParamsUntyped := rOperation.GetParameters() rParamsUntyped := rOperation.GetParameters()
if !lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() { if !lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() {
lParams := lParamsUntyped.Value.([]low.ValueReference[*v2.Parameter]) lParams := lParamsUntyped.Value.([]low.ValueReference[*v2.Parameter])
rParams := rParamsUntyped.Value.([]low.ValueReference[*v2.Parameter]) rParams := rParamsUntyped.Value.([]low.ValueReference[*v2.Parameter])
lv := make(map[string]*v2.Parameter, len(lParams)) lv := make(map[string]*v2.Parameter, len(lParams))
rv := make(map[string]*v2.Parameter, len(rParams)) rv := make(map[string]*v2.Parameter, len(rParams))
for i := range lParams { for i := range lParams {
s := lParams[i].Value.Name.Value s := lParams[i].Value.Name.Value
lv[s] = lParams[i].Value lv[s] = lParams[i].Value
} }
for i := range rParams { for i := range rParams {
s := rParams[i].Value.Name.Value s := rParams[i].Value.Name.Value
rv[s] = rParams[i].Value rv[s] = rParams[i].Value
} }
var paramChanges []*ParameterChanges var paramChanges []*ParameterChanges
for n := range lv { for n := range lv {
if _, ok := rv[n]; ok { if _, ok := rv[n]; ok {
if !low.AreEqual(lv[n], rv[n]) { if !low.AreEqual(lv[n], rv[n]) {
ch := CompareParameters(lv[n], rv[n]) ch := CompareParameters(lv[n], rv[n])
if ch != nil { if ch != nil {
paramChanges = append(paramChanges, ch) paramChanges = append(paramChanges, ch)
} }
} }
continue continue
} }
CreateChange(&changes, ObjectRemoved, v3.ParametersLabel, CreateChange(&changes, ObjectRemoved, v3.ParametersLabel,
lv[n].Name.ValueNode, nil, true, lv[n].Name.Value, lv[n].Name.ValueNode, nil, true, lv[n].Name.Value,
nil) nil)
} }
for n := range rv { for n := range rv {
if _, ok := lv[n]; !ok { if _, ok := lv[n]; !ok {
CreateChange(&changes, ObjectAdded, v3.ParametersLabel, CreateChange(&changes, ObjectAdded, v3.ParametersLabel,
nil, rv[n].Name.ValueNode, true, nil, nil, rv[n].Name.ValueNode, true, nil,
rv[n].Name.Value) rv[n].Name.Value)
} }
} }
oc.ParameterChanges = paramChanges oc.ParameterChanges = paramChanges
} }
if !lParamsUntyped.IsEmpty() && rParamsUntyped.IsEmpty() { if !lParamsUntyped.IsEmpty() && rParamsUntyped.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.ParametersLabel, CreateChange(&changes, PropertyRemoved, v3.ParametersLabel,
lParamsUntyped.ValueNode, nil, true, lParamsUntyped.Value, lParamsUntyped.ValueNode, nil, true, lParamsUntyped.Value,
nil) nil)
} }
if lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() { if lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.ParametersLabel, CreateChange(&changes, PropertyAdded, v3.ParametersLabel,
nil, rParamsUntyped.ValueNode, true, nil, nil, rParamsUntyped.ValueNode, true, nil,
rParamsUntyped.Value) rParamsUntyped.Value)
} }
// security // security
if !lOperation.Security.IsEmpty() || !rOperation.Security.IsEmpty() { if !lOperation.Security.IsEmpty() || !rOperation.Security.IsEmpty() {
checkSecurity(lOperation.Security, rOperation.Security, &changes, oc) checkSecurity(lOperation.Security, rOperation.Security, &changes, oc)
} }
// produces // produces
if len(lOperation.Produces.Value) > 0 || len(rOperation.Produces.Value) > 0 { if len(lOperation.Produces.Value) > 0 || len(rOperation.Produces.Value) > 0 {
ExtractStringValueSliceChanges(lOperation.Produces.Value, rOperation.Produces.Value, ExtractStringValueSliceChanges(lOperation.Produces.Value, rOperation.Produces.Value,
&changes, v3.ProducesLabel, true) &changes, v3.ProducesLabel, true)
} }
// consumes // consumes
if len(lOperation.Consumes.Value) > 0 || len(rOperation.Consumes.Value) > 0 { if len(lOperation.Consumes.Value) > 0 || len(rOperation.Consumes.Value) > 0 {
ExtractStringValueSliceChanges(lOperation.Consumes.Value, rOperation.Consumes.Value, ExtractStringValueSliceChanges(lOperation.Consumes.Value, rOperation.Consumes.Value,
&changes, v3.ConsumesLabel, true) &changes, v3.ConsumesLabel, true)
} }
// schemes // schemes
if len(lOperation.Schemes.Value) > 0 || len(rOperation.Schemes.Value) > 0 { if len(lOperation.Schemes.Value) > 0 || len(rOperation.Schemes.Value) > 0 {
ExtractStringValueSliceChanges(lOperation.Schemes.Value, rOperation.Schemes.Value, ExtractStringValueSliceChanges(lOperation.Schemes.Value, rOperation.Schemes.Value,
&changes, v3.SchemesLabel, true) &changes, v3.SchemesLabel, true)
} }
oc.ExtensionChanges = CompareExtensions(lOperation.Extensions, rOperation.Extensions) oc.ExtensionChanges = CompareExtensions(lOperation.Extensions, rOperation.Extensions)
} }
// OpenAPI // OpenAPI
if reflect.TypeOf(&v3.Operation{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v3.Operation{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v3.Operation{}) == reflect.TypeOf(r) { reflect.TypeOf(&v3.Operation{}) == reflect.TypeOf(r) {
lOperation := l.(*v3.Operation) lOperation := l.(*v3.Operation)
rOperation := r.(*v3.Operation) rOperation := r.(*v3.Operation)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lOperation, rOperation) { if low.AreEqual(lOperation, rOperation) {
return nil return nil
} }
props = append(props, addSharedOperationProperties(lOperation, rOperation, &changes)...) props = append(props, addSharedOperationProperties(lOperation, rOperation, &changes)...)
compareSharedOperationObjects(lOperation, rOperation, &changes, oc) compareSharedOperationObjects(lOperation, rOperation, &changes, oc)
// parameters // parameters
lParamsUntyped := lOperation.GetParameters() lParamsUntyped := lOperation.GetParameters()
rParamsUntyped := rOperation.GetParameters() rParamsUntyped := rOperation.GetParameters()
if !lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() { if !lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() {
lParams := lParamsUntyped.Value.([]low.ValueReference[*v3.Parameter]) lParams := lParamsUntyped.Value.([]low.ValueReference[*v3.Parameter])
rParams := rParamsUntyped.Value.([]low.ValueReference[*v3.Parameter]) rParams := rParamsUntyped.Value.([]low.ValueReference[*v3.Parameter])
lv := make(map[string]*v3.Parameter, len(lParams)) lv := make(map[string]*v3.Parameter, len(lParams))
rv := make(map[string]*v3.Parameter, len(rParams)) rv := make(map[string]*v3.Parameter, len(rParams))
for i := range lParams { for i := range lParams {
s := lParams[i].Value.Name.Value s := lParams[i].Value.Name.Value
lv[s] = lParams[i].Value lv[s] = lParams[i].Value
} }
for i := range rParams { for i := range rParams {
s := rParams[i].Value.Name.Value s := rParams[i].Value.Name.Value
rv[s] = rParams[i].Value rv[s] = rParams[i].Value
} }
var paramChanges []*ParameterChanges var paramChanges []*ParameterChanges
for n := range lv { for n := range lv {
if _, ok := rv[n]; ok { if _, ok := rv[n]; ok {
if !low.AreEqual(lv[n], rv[n]) { if !low.AreEqual(lv[n], rv[n]) {
ch := CompareParameters(lv[n], rv[n]) ch := CompareParameters(lv[n], rv[n])
if ch != nil { if ch != nil {
paramChanges = append(paramChanges, ch) paramChanges = append(paramChanges, ch)
} }
} }
continue continue
} }
CreateChange(&changes, ObjectRemoved, v3.ParametersLabel, CreateChange(&changes, ObjectRemoved, v3.ParametersLabel,
lv[n].Name.ValueNode, nil, true, lv[n].Name.Value, lv[n].Name.ValueNode, nil, true, lv[n].Name.Value,
nil) nil)
} }
for n := range rv { for n := range rv {
if _, ok := lv[n]; !ok { if _, ok := lv[n]; !ok {
CreateChange(&changes, ObjectAdded, v3.ParametersLabel, CreateChange(&changes, ObjectAdded, v3.ParametersLabel,
nil, rv[n].Name.ValueNode, true, nil, nil, rv[n].Name.ValueNode, true, nil,
rv[n].Name.Value) rv[n].Name.Value)
} }
} }
oc.ParameterChanges = paramChanges oc.ParameterChanges = paramChanges
} }
if !lParamsUntyped.IsEmpty() && rParamsUntyped.IsEmpty() { if !lParamsUntyped.IsEmpty() && rParamsUntyped.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.ParametersLabel, CreateChange(&changes, PropertyRemoved, v3.ParametersLabel,
lParamsUntyped.ValueNode, nil, true, lParamsUntyped.Value, lParamsUntyped.ValueNode, nil, true, lParamsUntyped.Value,
nil) nil)
} }
if lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() { if lParamsUntyped.IsEmpty() && !rParamsUntyped.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.ParametersLabel, CreateChange(&changes, PropertyAdded, v3.ParametersLabel,
nil, rParamsUntyped.ValueNode, true, nil, nil, rParamsUntyped.ValueNode, true, nil,
rParamsUntyped.Value) rParamsUntyped.Value)
} }
// security // security
if !lOperation.Security.IsEmpty() && !lOperation.Security.IsEmpty() { if !lOperation.Security.IsEmpty() && !lOperation.Security.IsEmpty() {
checkSecurity(lOperation.Security, rOperation.Security, &changes, oc) checkSecurity(lOperation.Security, rOperation.Security, &changes, oc)
} }
// request body // request body
if !lOperation.RequestBody.IsEmpty() && !rOperation.RequestBody.IsEmpty() { if !lOperation.RequestBody.IsEmpty() && !rOperation.RequestBody.IsEmpty() {
if !low.AreEqual(lOperation.RequestBody.Value, rOperation.RequestBody.Value) { if !low.AreEqual(lOperation.RequestBody.Value, rOperation.RequestBody.Value) {
oc.RequestBodyChanges = CompareRequestBodies(lOperation.RequestBody.Value, rOperation.RequestBody.Value) oc.RequestBodyChanges = CompareRequestBodies(lOperation.RequestBody.Value, rOperation.RequestBody.Value)
} }
} }
if !lOperation.RequestBody.IsEmpty() && rOperation.RequestBody.IsEmpty() { if !lOperation.RequestBody.IsEmpty() && rOperation.RequestBody.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.RequestBodyLabel, CreateChange(&changes, PropertyRemoved, v3.RequestBodyLabel,
lOperation.RequestBody.ValueNode, nil, true, lOperation.RequestBody.Value, lOperation.RequestBody.ValueNode, nil, true, lOperation.RequestBody.Value,
nil) nil)
} }
if lOperation.RequestBody.IsEmpty() && !rOperation.RequestBody.IsEmpty() { if lOperation.RequestBody.IsEmpty() && !rOperation.RequestBody.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.RequestBodyLabel, CreateChange(&changes, PropertyAdded, v3.RequestBodyLabel,
nil, rOperation.RequestBody.ValueNode, true, nil, nil, rOperation.RequestBody.ValueNode, true, nil,
rOperation.RequestBody.Value) rOperation.RequestBody.Value)
} }
// callbacks // callbacks
if !lOperation.GetCallbacks().IsEmpty() && !rOperation.GetCallbacks().IsEmpty() { if !lOperation.GetCallbacks().IsEmpty() && !rOperation.GetCallbacks().IsEmpty() {
oc.CallbackChanges = CheckMapForChanges(lOperation.Callbacks.Value, rOperation.Callbacks.Value, &changes, oc.CallbackChanges = CheckMapForChanges(lOperation.Callbacks.Value, rOperation.Callbacks.Value, &changes,
v3.CallbacksLabel, CompareCallback) v3.CallbacksLabel, CompareCallback)
} }
if !lOperation.GetCallbacks().IsEmpty() && rOperation.GetCallbacks().IsEmpty() { if !lOperation.GetCallbacks().IsEmpty() && rOperation.GetCallbacks().IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.CallbacksLabel, CreateChange(&changes, PropertyRemoved, v3.CallbacksLabel,
lOperation.Callbacks.ValueNode, nil, true, lOperation.Callbacks.Value, lOperation.Callbacks.ValueNode, nil, true, lOperation.Callbacks.Value,
nil) nil)
} }
if lOperation.Callbacks.IsEmpty() && !rOperation.Callbacks.IsEmpty() { if lOperation.Callbacks.IsEmpty() && !rOperation.Callbacks.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.CallbacksLabel, CreateChange(&changes, PropertyAdded, v3.CallbacksLabel,
nil, rOperation.Callbacks.ValueNode, true, nil, nil, rOperation.Callbacks.ValueNode, true, nil,
rOperation.Callbacks.Value) rOperation.Callbacks.Value)
} }
// servers // servers
oc.ServerChanges = checkServers(lOperation.Servers, rOperation.Servers) oc.ServerChanges = checkServers(lOperation.Servers, rOperation.Servers)
oc.ExtensionChanges = CompareExtensions(lOperation.Extensions, rOperation.Extensions) oc.ExtensionChanges = CompareExtensions(lOperation.Extensions, rOperation.Extensions)
// todo: callbacks // todo: callbacks
} }
CheckProperties(props) CheckProperties(props)
oc.Changes = changes oc.PropertyChanges = NewPropertyChanges(changes)
return oc return oc
} }
// check servers property // check servers property
func checkServers(lServers, rServers low.NodeReference[[]low.ValueReference[*v3.Server]]) []*ServerChanges { func checkServers(lServers, rServers low.NodeReference[[]low.ValueReference[*v3.Server]]) []*ServerChanges {
var serverChanges []*ServerChanges var serverChanges []*ServerChanges
if !lServers.IsEmpty() && !rServers.IsEmpty() { if !lServers.IsEmpty() && !rServers.IsEmpty() {
lv := make(map[string]low.ValueReference[*v3.Server], len(lServers.Value)) lv := make(map[string]low.ValueReference[*v3.Server], len(lServers.Value))
rv := make(map[string]low.ValueReference[*v3.Server], len(rServers.Value)) rv := make(map[string]low.ValueReference[*v3.Server], len(rServers.Value))
for i := range lServers.Value { for i := range lServers.Value {
var s string var s string
if !lServers.Value[i].Value.URL.IsEmpty() { if !lServers.Value[i].Value.URL.IsEmpty() {
s = lServers.Value[i].Value.URL.Value s = lServers.Value[i].Value.URL.Value
} else { } else {
s = low.GenerateHashString(lServers.Value[i].Value) s = low.GenerateHashString(lServers.Value[i].Value)
} }
lv[s] = lServers.Value[i] lv[s] = lServers.Value[i]
} }
for i := range rServers.Value { for i := range rServers.Value {
var s string var s string
if !rServers.Value[i].Value.URL.IsEmpty() { if !rServers.Value[i].Value.URL.IsEmpty() {
s = rServers.Value[i].Value.URL.Value s = rServers.Value[i].Value.URL.Value
} else { } else {
s = low.GenerateHashString(rServers.Value[i].Value) s = low.GenerateHashString(rServers.Value[i].Value)
} }
rv[s] = rServers.Value[i] rv[s] = rServers.Value[i]
} }
for k := range lv { for k := range lv {
var changes []*Change var changes []*Change
if _, ok := rv[k]; ok { if _, ok := rv[k]; ok {
if !low.AreEqual(lv[k].Value, rv[k].Value) { if !low.AreEqual(lv[k].Value, rv[k].Value) {
serverChanges = append(serverChanges, CompareServers(lv[k].Value, rv[k].Value)) serverChanges = append(serverChanges, CompareServers(lv[k].Value, rv[k].Value))
} }
continue continue
} }
lv[k].ValueNode.Value = lv[k].Value.URL.Value lv[k].ValueNode.Value = lv[k].Value.URL.Value
CreateChange(&changes, ObjectRemoved, v3.ServersLabel, CreateChange(&changes, ObjectRemoved, v3.ServersLabel,
lv[k].ValueNode, nil, true, lv[k].Value.URL.Value, lv[k].ValueNode, nil, true, lv[k].Value.URL.Value,
nil) nil)
sc := new(ServerChanges) sc := new(ServerChanges)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
serverChanges = append(serverChanges, sc) serverChanges = append(serverChanges, sc)
} }
for k := range rv { for k := range rv {
if _, ok := lv[k]; !ok { if _, ok := lv[k]; !ok {
var changes []*Change var changes []*Change
rv[k].ValueNode.Value = rv[k].Value.URL.Value rv[k].ValueNode.Value = rv[k].Value.URL.Value
CreateChange(&changes, ObjectAdded, v3.ServersLabel, CreateChange(&changes, ObjectAdded, v3.ServersLabel,
nil, rv[k].ValueNode, false, nil, nil, rv[k].ValueNode, false, nil,
rv[k].Value.URL.Value) rv[k].Value.URL.Value)
sc := new(ServerChanges) sc := new(ServerChanges)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
serverChanges = append(serverChanges, sc) serverChanges = append(serverChanges, sc)
} }
} }
} }
var changes []*Change var changes []*Change
sc := new(ServerChanges) sc := new(ServerChanges)
if !lServers.IsEmpty() && rServers.IsEmpty() { if !lServers.IsEmpty() && rServers.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.ServersLabel, CreateChange(&changes, PropertyRemoved, v3.ServersLabel,
lServers.ValueNode, nil, true, lServers.Value, lServers.ValueNode, nil, true, lServers.Value,
nil) nil)
} }
if lServers.IsEmpty() && !rServers.IsEmpty() { if lServers.IsEmpty() && !rServers.IsEmpty() {
CreateChange(&changes, PropertyAdded, v3.ServersLabel, CreateChange(&changes, PropertyAdded, v3.ServersLabel,
nil, rServers.ValueNode, false, nil, nil, rServers.ValueNode, false, nil,
rServers.Value) rServers.Value)
} }
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
if len(changes) > 0 { if len(changes) > 0 {
serverChanges = append(serverChanges, sc) serverChanges = append(serverChanges, sc)
} }
if len(serverChanges) <= 0 { if len(serverChanges) <= 0 {
return nil return nil
} }
return serverChanges return serverChanges
} }
// check security property. // check security property.
func checkSecurity(lSecurity, rSecurity low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]], func checkSecurity(lSecurity, rSecurity low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]],
changes *[]*Change, oc any) { changes *[]*Change, oc any) {
lv := make(map[string]*base.SecurityRequirement, len(lSecurity.Value)) lv := make(map[string]*base.SecurityRequirement, len(lSecurity.Value))
rv := make(map[string]*base.SecurityRequirement, len(rSecurity.Value)) rv := make(map[string]*base.SecurityRequirement, len(rSecurity.Value))
lvn := make(map[string]*yaml.Node, len(lSecurity.Value)) lvn := make(map[string]*yaml.Node, len(lSecurity.Value))
rvn := make(map[string]*yaml.Node, len(rSecurity.Value)) rvn := make(map[string]*yaml.Node, len(rSecurity.Value))
for i := range lSecurity.Value { for i := range lSecurity.Value {
keys := lSecurity.Value[i].Value.GetKeys() keys := lSecurity.Value[i].Value.GetKeys()
sort.Strings(keys) sort.Strings(keys)
s := strings.Join(keys, "|") s := strings.Join(keys, "|")
lv[s] = lSecurity.Value[i].Value lv[s] = lSecurity.Value[i].Value
lvn[s] = lSecurity.Value[i].ValueNode lvn[s] = lSecurity.Value[i].ValueNode
} }
for i := range rSecurity.Value { for i := range rSecurity.Value {
keys := rSecurity.Value[i].Value.GetKeys() keys := rSecurity.Value[i].Value.GetKeys()
sort.Strings(keys) sort.Strings(keys)
s := strings.Join(keys, "|") s := strings.Join(keys, "|")
rv[s] = rSecurity.Value[i].Value rv[s] = rSecurity.Value[i].Value
rvn[s] = rSecurity.Value[i].ValueNode rvn[s] = rSecurity.Value[i].ValueNode
} }
var secChanges []*SecurityRequirementChanges var secChanges []*SecurityRequirementChanges
for n := range lv { for n := range lv {
if _, ok := rv[n]; ok { if _, ok := rv[n]; ok {
if !low.AreEqual(lv[n], rv[n]) { if !low.AreEqual(lv[n], rv[n]) {
ch := CompareSecurityRequirement(lv[n], rv[n]) ch := CompareSecurityRequirement(lv[n], rv[n])
if ch != nil { if ch != nil {
secChanges = append(secChanges, ch) secChanges = append(secChanges, ch)
} }
} }
continue continue
} }
lvn[n].Value = strings.Join(lv[n].GetKeys(), ", ") lvn[n].Value = strings.Join(lv[n].GetKeys(), ", ")
CreateChange(changes, ObjectRemoved, v3.SecurityLabel, CreateChange(changes, ObjectRemoved, v3.SecurityLabel,
lvn[n], nil, true, lv[n], lvn[n], nil, true, lv[n],
nil) nil)
} }
for n := range rv { for n := range rv {
if _, ok := lv[n]; !ok { if _, ok := lv[n]; !ok {
rvn[n].Value = strings.Join(rv[n].GetKeys(), ", ") rvn[n].Value = strings.Join(rv[n].GetKeys(), ", ")
CreateChange(changes, ObjectAdded, v3.SecurityLabel, CreateChange(changes, ObjectAdded, v3.SecurityLabel,
nil, rvn[n], false, nil, nil, rvn[n], false, nil,
rv[n]) rv[n])
} }
} }
// handle different change types. // handle different change types.
if reflect.TypeOf(&OperationChanges{}) == reflect.TypeOf(oc) { if reflect.TypeOf(&OperationChanges{}) == reflect.TypeOf(oc) {
oc.(*OperationChanges).SecurityRequirementChanges = secChanges oc.(*OperationChanges).SecurityRequirementChanges = secChanges
} }
if reflect.TypeOf(&DocumentChanges{}) == reflect.TypeOf(oc) { if reflect.TypeOf(&DocumentChanges{}) == reflect.TypeOf(oc) {
oc.(*DocumentChanges).SecurityRequirementChanges = secChanges oc.(*DocumentChanges).SecurityRequirementChanges = secChanges
} }
} }

View File

@@ -4,332 +4,332 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"reflect" "reflect"
) )
// ParameterChanges represents changes found between Swagger or OpenAPI Parameter objects. // ParameterChanges represents changes found between Swagger or OpenAPI Parameter objects.
type ParameterChanges struct { type ParameterChanges struct {
PropertyChanges *PropertyChanges
SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"` SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
// Swagger supports Items. // Swagger supports Items.
ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"` ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"`
// OpenAPI supports examples and content types. // OpenAPI supports examples and content types.
ExamplesChanges map[string]*ExampleChanges `json:"examples,omitempty" yaml:"examples,omitempty"` ExamplesChanges map[string]*ExampleChanges `json:"examples,omitempty" yaml:"examples,omitempty"`
ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"` ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"`
} }
// TotalChanges returns a count of everything that changed // TotalChanges returns a count of everything that changed
func (p *ParameterChanges) TotalChanges() int { func (p *ParameterChanges) TotalChanges() int {
c := p.PropertyChanges.TotalChanges() c := p.PropertyChanges.TotalChanges()
if p.SchemaChanges != nil { if p.SchemaChanges != nil {
c += p.SchemaChanges.TotalChanges() c += p.SchemaChanges.TotalChanges()
} }
for i := range p.ExamplesChanges { for i := range p.ExamplesChanges {
c += p.ExamplesChanges[i].TotalChanges() c += p.ExamplesChanges[i].TotalChanges()
} }
if p.ItemsChanges != nil { if p.ItemsChanges != nil {
c += p.ItemsChanges.TotalChanges() c += p.ItemsChanges.TotalChanges()
} }
if p.ExtensionChanges != nil { if p.ExtensionChanges != nil {
c += p.ExtensionChanges.TotalChanges() c += p.ExtensionChanges.TotalChanges()
} }
for i := range p.ContentChanges { for i := range p.ContentChanges {
c += p.ContentChanges[i].TotalChanges() c += p.ContentChanges[i].TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges always returns 0 for ExternalDoc objects, they are non-binding. // TotalBreakingChanges always returns 0 for ExternalDoc objects, they are non-binding.
func (p *ParameterChanges) TotalBreakingChanges() int { func (p *ParameterChanges) TotalBreakingChanges() int {
c := p.PropertyChanges.TotalBreakingChanges() c := p.PropertyChanges.TotalBreakingChanges()
if p.SchemaChanges != nil { if p.SchemaChanges != nil {
c += p.SchemaChanges.TotalBreakingChanges() c += p.SchemaChanges.TotalBreakingChanges()
} }
if p.ItemsChanges != nil { if p.ItemsChanges != nil {
c += p.ItemsChanges.TotalBreakingChanges() c += p.ItemsChanges.TotalBreakingChanges()
} }
for i := range p.ContentChanges { for i := range p.ContentChanges {
c += p.ContentChanges[i].TotalBreakingChanges() c += p.ContentChanges[i].TotalBreakingChanges()
} }
return c return c
} }
func addPropertyCheck(props *[]*PropertyCheck, func addPropertyCheck(props *[]*PropertyCheck,
lvn, rvn *yaml.Node, lv, rv any, changes *[]*Change, label string, breaking bool) { lvn, rvn *yaml.Node, lv, rv any, changes *[]*Change, label string, breaking bool) {
*props = append(*props, &PropertyCheck{ *props = append(*props, &PropertyCheck{
LeftNode: lvn, LeftNode: lvn,
RightNode: rvn, RightNode: rvn,
Label: label, Label: label,
Changes: changes, Changes: changes,
Breaking: breaking, Breaking: breaking,
Original: lv, Original: lv,
New: rv, New: rv,
}) })
} }
func addOpenAPIParameterProperties(left, right low.OpenAPIParameter, changes *[]*Change) []*PropertyCheck { func addOpenAPIParameterProperties(left, right low.OpenAPIParameter, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
// style // style
addPropertyCheck(&props, left.GetStyle().ValueNode, right.GetStyle().ValueNode, addPropertyCheck(&props, left.GetStyle().ValueNode, right.GetStyle().ValueNode,
left.GetStyle(), right.GetStyle(), changes, v3.StyleLabel, false) left.GetStyle(), right.GetStyle(), changes, v3.StyleLabel, false)
// allow reserved // allow reserved
addPropertyCheck(&props, left.GetAllowReserved().ValueNode, right.GetAllowReserved().ValueNode, addPropertyCheck(&props, left.GetAllowReserved().ValueNode, right.GetAllowReserved().ValueNode,
left.GetAllowReserved(), right.GetAllowReserved(), changes, v3.AllowReservedLabel, true) left.GetAllowReserved(), right.GetAllowReserved(), changes, v3.AllowReservedLabel, true)
// explode // explode
addPropertyCheck(&props, left.GetExplode().ValueNode, right.GetExplode().ValueNode, addPropertyCheck(&props, left.GetExplode().ValueNode, right.GetExplode().ValueNode,
left.GetExplode(), right.GetExplode(), changes, v3.ExplodeLabel, false) left.GetExplode(), right.GetExplode(), changes, v3.ExplodeLabel, false)
// deprecated // deprecated
addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode, addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode,
left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false) left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false)
// example // example
addPropertyCheck(&props, left.GetExample().ValueNode, right.GetExample().ValueNode, addPropertyCheck(&props, left.GetExample().ValueNode, right.GetExample().ValueNode,
left.GetExample(), right.GetExample(), changes, v3.ExampleLabel, false) left.GetExample(), right.GetExample(), changes, v3.ExampleLabel, false)
return props return props
} }
func addSwaggerParameterProperties(left, right low.SwaggerParameter, changes *[]*Change) []*PropertyCheck { func addSwaggerParameterProperties(left, right low.SwaggerParameter, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
// type // type
addPropertyCheck(&props, left.GetType().ValueNode, right.GetType().ValueNode, addPropertyCheck(&props, left.GetType().ValueNode, right.GetType().ValueNode,
left.GetType(), right.GetType(), changes, v3.TypeLabel, true) left.GetType(), right.GetType(), changes, v3.TypeLabel, true)
// format // format
addPropertyCheck(&props, left.GetFormat().ValueNode, right.GetFormat().ValueNode, addPropertyCheck(&props, left.GetFormat().ValueNode, right.GetFormat().ValueNode,
left.GetFormat(), right.GetFormat(), changes, v3.FormatLabel, true) left.GetFormat(), right.GetFormat(), changes, v3.FormatLabel, true)
// collection format // collection format
addPropertyCheck(&props, left.GetCollectionFormat().ValueNode, right.GetCollectionFormat().ValueNode, addPropertyCheck(&props, left.GetCollectionFormat().ValueNode, right.GetCollectionFormat().ValueNode,
left.GetCollectionFormat(), right.GetCollectionFormat(), changes, v3.CollectionFormatLabel, true) left.GetCollectionFormat(), right.GetCollectionFormat(), changes, v3.CollectionFormatLabel, true)
// maximum // maximum
addPropertyCheck(&props, left.GetMaximum().ValueNode, right.GetMaximum().ValueNode, addPropertyCheck(&props, left.GetMaximum().ValueNode, right.GetMaximum().ValueNode,
left.GetMaximum(), right.GetMaximum(), changes, v3.MaximumLabel, true) left.GetMaximum(), right.GetMaximum(), changes, v3.MaximumLabel, true)
// minimum // minimum
addPropertyCheck(&props, left.GetMinimum().ValueNode, right.GetMinimum().ValueNode, addPropertyCheck(&props, left.GetMinimum().ValueNode, right.GetMinimum().ValueNode,
left.GetMinimum(), right.GetMinimum(), changes, v3.MinimumLabel, true) left.GetMinimum(), right.GetMinimum(), changes, v3.MinimumLabel, true)
// exclusive maximum // exclusive maximum
addPropertyCheck(&props, left.GetExclusiveMaximum().ValueNode, right.GetExclusiveMaximum().ValueNode, addPropertyCheck(&props, left.GetExclusiveMaximum().ValueNode, right.GetExclusiveMaximum().ValueNode,
left.GetExclusiveMaximum(), right.GetExclusiveMaximum(), changes, v3.ExclusiveMaximumLabel, true) left.GetExclusiveMaximum(), right.GetExclusiveMaximum(), changes, v3.ExclusiveMaximumLabel, true)
// exclusive minimum // exclusive minimum
addPropertyCheck(&props, left.GetExclusiveMinimum().ValueNode, right.GetExclusiveMinimum().ValueNode, addPropertyCheck(&props, left.GetExclusiveMinimum().ValueNode, right.GetExclusiveMinimum().ValueNode,
left.GetExclusiveMinimum(), right.GetExclusiveMinimum(), changes, v3.ExclusiveMinimumLabel, true) left.GetExclusiveMinimum(), right.GetExclusiveMinimum(), changes, v3.ExclusiveMinimumLabel, true)
// max length // max length
addPropertyCheck(&props, left.GetMaxLength().ValueNode, right.GetMaxLength().ValueNode, addPropertyCheck(&props, left.GetMaxLength().ValueNode, right.GetMaxLength().ValueNode,
left.GetMaxLength(), right.GetMaxLength(), changes, v3.MaxLengthLabel, true) left.GetMaxLength(), right.GetMaxLength(), changes, v3.MaxLengthLabel, true)
// min length // min length
addPropertyCheck(&props, left.GetMinLength().ValueNode, right.GetMinLength().ValueNode, addPropertyCheck(&props, left.GetMinLength().ValueNode, right.GetMinLength().ValueNode,
left.GetMinLength(), right.GetMinLength(), changes, v3.MinLengthLabel, true) left.GetMinLength(), right.GetMinLength(), changes, v3.MinLengthLabel, true)
// pattern // pattern
addPropertyCheck(&props, left.GetPattern().ValueNode, right.GetPattern().ValueNode, addPropertyCheck(&props, left.GetPattern().ValueNode, right.GetPattern().ValueNode,
left.GetPattern(), right.GetPattern(), changes, v3.PatternLabel, true) left.GetPattern(), right.GetPattern(), changes, v3.PatternLabel, true)
// max items // max items
addPropertyCheck(&props, left.GetMaxItems().ValueNode, right.GetMaxItems().ValueNode, addPropertyCheck(&props, left.GetMaxItems().ValueNode, right.GetMaxItems().ValueNode,
left.GetMaxItems(), right.GetMaxItems(), changes, v3.MaxItemsLabel, true) left.GetMaxItems(), right.GetMaxItems(), changes, v3.MaxItemsLabel, true)
// min items // min items
addPropertyCheck(&props, left.GetMinItems().ValueNode, right.GetMinItems().ValueNode, addPropertyCheck(&props, left.GetMinItems().ValueNode, right.GetMinItems().ValueNode,
left.GetMinItems(), right.GetMinItems(), changes, v3.MinItemsLabel, true) left.GetMinItems(), right.GetMinItems(), changes, v3.MinItemsLabel, true)
// unique items // unique items
addPropertyCheck(&props, left.GetUniqueItems().ValueNode, right.GetUniqueItems().ValueNode, addPropertyCheck(&props, left.GetUniqueItems().ValueNode, right.GetUniqueItems().ValueNode,
left.GetUniqueItems(), right.GetUniqueItems(), changes, v3.UniqueItemsLabel, true) left.GetUniqueItems(), right.GetUniqueItems(), changes, v3.UniqueItemsLabel, true)
// default // default
addPropertyCheck(&props, left.GetDefault().ValueNode, right.GetDefault().ValueNode, addPropertyCheck(&props, left.GetDefault().ValueNode, right.GetDefault().ValueNode,
left.GetDefault(), right.GetDefault(), changes, v3.DefaultLabel, true) left.GetDefault(), right.GetDefault(), changes, v3.DefaultLabel, true)
// multiple of // multiple of
addPropertyCheck(&props, left.GetMultipleOf().ValueNode, right.GetMultipleOf().ValueNode, addPropertyCheck(&props, left.GetMultipleOf().ValueNode, right.GetMultipleOf().ValueNode,
left.GetMultipleOf(), right.GetMultipleOf(), changes, v3.MultipleOfLabel, true) left.GetMultipleOf(), right.GetMultipleOf(), changes, v3.MultipleOfLabel, true)
return props return props
} }
func addCommonParameterProperties(left, right low.SharedParameters, changes *[]*Change) []*PropertyCheck { func addCommonParameterProperties(left, right low.SharedParameters, changes *[]*Change) []*PropertyCheck {
var props []*PropertyCheck var props []*PropertyCheck
addPropertyCheck(&props, left.GetName().ValueNode, right.GetName().ValueNode, addPropertyCheck(&props, left.GetName().ValueNode, right.GetName().ValueNode,
left.GetName(), right.GetName(), changes, v3.NameLabel, true) left.GetName(), right.GetName(), changes, v3.NameLabel, true)
// in // in
addPropertyCheck(&props, left.GetIn().ValueNode, right.GetIn().ValueNode, addPropertyCheck(&props, left.GetIn().ValueNode, right.GetIn().ValueNode,
left.GetIn(), right.GetIn(), changes, v3.InLabel, true) left.GetIn(), right.GetIn(), changes, v3.InLabel, true)
// description // description
addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode, addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode,
left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false) left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false)
// required // required
addPropertyCheck(&props, left.GetRequired().ValueNode, right.GetRequired().ValueNode, addPropertyCheck(&props, left.GetRequired().ValueNode, right.GetRequired().ValueNode,
left.GetRequired(), right.GetRequired(), changes, v3.RequiredLabel, true) left.GetRequired(), right.GetRequired(), changes, v3.RequiredLabel, true)
// allow empty value // allow empty value
addPropertyCheck(&props, left.GetAllowEmptyValue().ValueNode, right.GetAllowEmptyValue().ValueNode, addPropertyCheck(&props, left.GetAllowEmptyValue().ValueNode, right.GetAllowEmptyValue().ValueNode,
left.GetAllowEmptyValue(), right.GetAllowEmptyValue(), changes, v3.AllowEmptyValueLabel, true) left.GetAllowEmptyValue(), right.GetAllowEmptyValue(), changes, v3.AllowEmptyValueLabel, true)
return props return props
} }
// CompareParametersV3 is amn OpenAPI type safe proxy for CompareParameters // CompareParametersV3 is amn OpenAPI type safe proxy for CompareParameters
func CompareParametersV3(l, r *v3.Parameter) *ParameterChanges { func CompareParametersV3(l, r *v3.Parameter) *ParameterChanges {
return CompareParameters(l, r) return CompareParameters(l, r)
} }
// CompareParameters compares a left and right Swagger or OpenAPI Parameter object for any changes. If found returns // CompareParameters compares a left and right Swagger or OpenAPI Parameter object for any changes. If found returns
// a pointer to ParameterChanges. If nothing is found, returns nil. // a pointer to ParameterChanges. If nothing is found, returns nil.
func CompareParameters(l, r any) *ParameterChanges { func CompareParameters(l, r any) *ParameterChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
pc := new(ParameterChanges) pc := new(ParameterChanges)
var lSchema *base.SchemaProxy var lSchema *base.SchemaProxy
var rSchema *base.SchemaProxy var rSchema *base.SchemaProxy
var lext, rext map[low.KeyReference[string]]low.ValueReference[any] var lext, rext map[low.KeyReference[string]]low.ValueReference[any]
if reflect.TypeOf(&v2.Parameter{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Parameter{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v2.Parameter{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Parameter{}) == reflect.TypeOf(r) {
lParam := l.(*v2.Parameter) lParam := l.(*v2.Parameter)
rParam := r.(*v2.Parameter) rParam := r.(*v2.Parameter)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lParam, rParam) { if low.AreEqual(lParam, rParam) {
return nil return nil
} }
props = append(props, addSwaggerParameterProperties(lParam, rParam, &changes)...) props = append(props, addSwaggerParameterProperties(lParam, rParam, &changes)...)
props = append(props, addCommonParameterProperties(lParam, rParam, &changes)...) props = append(props, addCommonParameterProperties(lParam, rParam, &changes)...)
// extract schema // extract schema
if lParam != nil { if lParam != nil {
lSchema = lParam.Schema.Value lSchema = lParam.Schema.Value
lext = lParam.Extensions lext = lParam.Extensions
} }
if rParam != nil { if rParam != nil {
rext = rParam.Extensions rext = rParam.Extensions
rSchema = rParam.Schema.Value rSchema = rParam.Schema.Value
} }
// items // items
if !lParam.Items.IsEmpty() && !rParam.Items.IsEmpty() { if !lParam.Items.IsEmpty() && !rParam.Items.IsEmpty() {
if lParam.Items.Value.Hash() != rParam.Items.Value.Hash() { if lParam.Items.Value.Hash() != rParam.Items.Value.Hash() {
pc.ItemsChanges = CompareItems(lParam.Items.Value, rParam.Items.Value) pc.ItemsChanges = CompareItems(lParam.Items.Value, rParam.Items.Value)
} }
} }
if lParam.Items.IsEmpty() && !rParam.Items.IsEmpty() { if lParam.Items.IsEmpty() && !rParam.Items.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ItemsLabel, CreateChange(&changes, ObjectAdded, v3.ItemsLabel,
nil, rParam.Items.ValueNode, true, nil, nil, rParam.Items.ValueNode, true, nil,
rParam.Items.Value) rParam.Items.Value)
} }
if !lParam.Items.IsEmpty() && rParam.Items.IsEmpty() { if !lParam.Items.IsEmpty() && rParam.Items.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.ItemsLabel, CreateChange(&changes, ObjectRemoved, v3.ItemsLabel,
lParam.Items.ValueNode, nil, true, lParam.Items.Value, lParam.Items.ValueNode, nil, true, lParam.Items.Value,
nil) nil)
} }
// enum // enum
if len(lParam.Enum.Value) > 0 || len(rParam.Enum.Value) > 0 { if len(lParam.Enum.Value) > 0 || len(rParam.Enum.Value) > 0 {
ExtractRawValueSliceChanges(lParam.Enum.Value, rParam.Enum.Value, &changes, v3.EnumLabel, true) ExtractRawValueSliceChanges(lParam.Enum.Value, rParam.Enum.Value, &changes, v3.EnumLabel, true)
} }
} }
// OpenAPI // OpenAPI
if reflect.TypeOf(&v3.Parameter{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Parameter{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v3.Parameter{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Parameter{}) == reflect.TypeOf(r) {
lParam := l.(*v3.Parameter) lParam := l.(*v3.Parameter)
rParam := r.(*v3.Parameter) rParam := r.(*v3.Parameter)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lParam, rParam) { if low.AreEqual(lParam, rParam) {
return nil return nil
} }
props = append(props, addOpenAPIParameterProperties(lParam, rParam, &changes)...) props = append(props, addOpenAPIParameterProperties(lParam, rParam, &changes)...)
props = append(props, addCommonParameterProperties(lParam, rParam, &changes)...) props = append(props, addCommonParameterProperties(lParam, rParam, &changes)...)
if lParam != nil { if lParam != nil {
lext = lParam.Extensions lext = lParam.Extensions
lSchema = lParam.Schema.Value lSchema = lParam.Schema.Value
} }
if rParam != nil { if rParam != nil {
rext = rParam.Extensions rext = rParam.Extensions
rSchema = rParam.Schema.Value rSchema = rParam.Schema.Value
} }
// example // example
checkParameterExample(lParam.Example, rParam.Example, changes) checkParameterExample(lParam.Example, rParam.Example, changes)
// examples // examples
pc.ExamplesChanges = CheckMapForChanges(lParam.Examples.Value, rParam.Examples.Value, pc.ExamplesChanges = CheckMapForChanges(lParam.Examples.Value, rParam.Examples.Value,
&changes, v3.ExamplesLabel, CompareExamples) &changes, v3.ExamplesLabel, CompareExamples)
// content // content
pc.ContentChanges = CheckMapForChanges(lParam.Content.Value, rParam.Content.Value, pc.ContentChanges = CheckMapForChanges(lParam.Content.Value, rParam.Content.Value,
&changes, v3.ContentLabel, CompareMediaTypes) &changes, v3.ContentLabel, CompareMediaTypes)
} }
CheckProperties(props) CheckProperties(props)
if lSchema != nil && rSchema != nil { if lSchema != nil && rSchema != nil {
pc.SchemaChanges = CompareSchemas(lSchema, rSchema) pc.SchemaChanges = CompareSchemas(lSchema, rSchema)
} }
if lSchema != nil && rSchema == nil { if lSchema != nil && rSchema == nil {
CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, CreateChange(&changes, ObjectRemoved, v3.SchemaLabel,
lSchema.GetValueNode(), nil, true, lSchema, lSchema.GetValueNode(), nil, true, lSchema,
nil) nil)
} }
if lSchema == nil && rSchema != nil { if lSchema == nil && rSchema != nil {
CreateChange(&changes, ObjectAdded, v3.SchemaLabel, CreateChange(&changes, ObjectAdded, v3.SchemaLabel,
nil, rSchema.GetValueNode(), true, nil, nil, rSchema.GetValueNode(), true, nil,
rSchema) rSchema)
} }
pc.Changes = changes pc.PropertyChanges = NewPropertyChanges(changes)
pc.ExtensionChanges = CompareExtensions(lext, rext) pc.ExtensionChanges = CompareExtensions(lext, rext)
if pc.TotalChanges() <= 0 { if pc.TotalChanges() <= 0 {
return nil return nil
} }
return pc return pc
} }
func checkParameterExample(expLeft, expRight low.NodeReference[any], changes []*Change) { func checkParameterExample(expLeft, expRight low.NodeReference[any], changes []*Change) {
if !expLeft.IsEmpty() && !expRight.IsEmpty() { if !expLeft.IsEmpty() && !expRight.IsEmpty() {
if low.GenerateHashString(expLeft.GetValue()) != low.GenerateHashString(expRight.GetValue()) { if low.GenerateHashString(expLeft.GetValue()) != low.GenerateHashString(expRight.GetValue()) {
CreateChange(&changes, Modified, v3.ExampleLabel, CreateChange(&changes, Modified, v3.ExampleLabel,
expLeft.GetValueNode(), expRight.GetValueNode(), false, expLeft.GetValueNode(), expRight.GetValueNode(), false,
expLeft.GetValue(), expRight.GetValue()) expLeft.GetValue(), expRight.GetValue())
} }
} }
if expLeft.Value == nil && expRight.Value != nil { if expLeft.Value == nil && expRight.Value != nil {
CreateChange(&changes, PropertyAdded, v3.ExampleLabel, CreateChange(&changes, PropertyAdded, v3.ExampleLabel,
nil, expRight.GetValueNode(), false, nil, expRight.GetValueNode(), false,
nil, expRight.GetValue()) nil, expRight.GetValue())
} }
if expLeft.Value != nil && expRight.Value == nil { if expLeft.Value != nil && expRight.Value == nil {
CreateChange(&changes, PropertyRemoved, v3.ExampleLabel, CreateChange(&changes, PropertyRemoved, v3.ExampleLabel,
expLeft.GetValueNode(), nil, false, expLeft.GetValueNode(), nil, false,
expLeft.GetValue(), nil) expLeft.GetValue(), nil)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,190 +4,190 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
"sync" "sync"
) )
// PathsChanges represents changes found between two Swagger or OpenAPI Paths Objects. // PathsChanges represents changes found between two Swagger or OpenAPI Paths Objects.
type PathsChanges struct { type PathsChanges struct {
PropertyChanges *PropertyChanges
PathItemsChanges map[string]*PathItemChanges `json:"pathItems,omitempty" yaml:"pathItems,omitempty"` PathItemsChanges map[string]*PathItemChanges `json:"pathItems,omitempty" yaml:"pathItems,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns the total number of changes between two Swagger or OpenAPI Paths Objects // TotalChanges returns the total number of changes between two Swagger or OpenAPI Paths Objects
func (p *PathsChanges) TotalChanges() int { func (p *PathsChanges) TotalChanges() int {
c := p.PropertyChanges.TotalChanges() c := p.PropertyChanges.TotalChanges()
for k := range p.PathItemsChanges { for k := range p.PathItemsChanges {
c += p.PathItemsChanges[k].TotalChanges() c += p.PathItemsChanges[k].TotalChanges()
} }
if p.ExtensionChanges != nil { if p.ExtensionChanges != nil {
c += p.ExtensionChanges.TotalChanges() c += p.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns tht total number of changes found between two Swagger or OpenAPI Path Objects // TotalBreakingChanges returns tht total number of changes found between two Swagger or OpenAPI Path Objects
func (p *PathsChanges) TotalBreakingChanges() int { func (p *PathsChanges) TotalBreakingChanges() int {
c := p.PropertyChanges.TotalBreakingChanges() c := p.PropertyChanges.TotalBreakingChanges()
for k := range p.PathItemsChanges { for k := range p.PathItemsChanges {
c += p.PathItemsChanges[k].TotalBreakingChanges() c += p.PathItemsChanges[k].TotalBreakingChanges()
} }
return c return c
} }
// ComparePaths compares a left and right Swagger or OpenAPI Paths Object for changes. If found, returns a pointer // ComparePaths compares a left and right Swagger or OpenAPI Paths Object for changes. If found, returns a pointer
// to a PathsChanges instance. Returns nil if nothing is found. // to a PathsChanges instance. Returns nil if nothing is found.
func ComparePaths(l, r any) *PathsChanges { func ComparePaths(l, r any) *PathsChanges {
var changes []*Change var changes []*Change
pc := new(PathsChanges) pc := new(PathsChanges)
pathChanges := make(map[string]*PathItemChanges) pathChanges := make(map[string]*PathItemChanges)
// Swagger // Swagger
if reflect.TypeOf(&v2.Paths{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.Paths{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.Paths{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.Paths{}) == reflect.TypeOf(r) {
lPath := l.(*v2.Paths) lPath := l.(*v2.Paths)
rPath := r.(*v2.Paths) rPath := r.(*v2.Paths)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lPath, rPath) { if low.AreEqual(lPath, rPath) {
return nil return nil
} }
lKeys := make(map[string]low.ValueReference[*v2.PathItem]) lKeys := make(map[string]low.ValueReference[*v2.PathItem])
rKeys := make(map[string]low.ValueReference[*v2.PathItem]) rKeys := make(map[string]low.ValueReference[*v2.PathItem])
for k := range lPath.PathItems { for k := range lPath.PathItems {
lKeys[k.Value] = lPath.PathItems[k] lKeys[k.Value] = lPath.PathItems[k]
} }
for k := range rPath.PathItems { for k := range rPath.PathItems {
rKeys[k.Value] = rPath.PathItems[k] rKeys[k.Value] = rPath.PathItems[k]
} }
// run every comparison in a thread. // run every comparison in a thread.
var mLock sync.Mutex var mLock sync.Mutex
compare := func(path string, pChanges map[string]*PathItemChanges, l, r *v2.PathItem, doneChan chan bool) { compare := func(path string, pChanges map[string]*PathItemChanges, l, r *v2.PathItem, doneChan chan bool) {
if !low.AreEqual(l, r) { if !low.AreEqual(l, r) {
mLock.Lock() mLock.Lock()
pathChanges[path] = ComparePathItems(l, r) pathChanges[path] = ComparePathItems(l, r)
mLock.Unlock() mLock.Unlock()
} }
doneChan <- true doneChan <- true
} }
doneChan := make(chan bool) doneChan := make(chan bool)
pathsChecked := 0 pathsChecked := 0
for k := range lKeys { for k := range lKeys {
if _, ok := rKeys[k]; ok { if _, ok := rKeys[k]; ok {
go compare(k, pathChanges, lKeys[k].Value, rKeys[k].Value, doneChan) go compare(k, pathChanges, lKeys[k].Value, rKeys[k].Value, doneChan)
pathsChecked++ pathsChecked++
continue continue
} }
g, p := lPath.FindPathAndKey(k) g, p := lPath.FindPathAndKey(k)
CreateChange(&changes, ObjectRemoved, v3.PathLabel, CreateChange(&changes, ObjectRemoved, v3.PathLabel,
g.KeyNode, nil, true, g.KeyNode, nil, true,
p.Value, nil) p.Value, nil)
} }
for k := range rKeys { for k := range rKeys {
if _, ok := lKeys[k]; !ok { if _, ok := lKeys[k]; !ok {
g, p := rPath.FindPathAndKey(k) g, p := rPath.FindPathAndKey(k)
CreateChange(&changes, ObjectAdded, v3.PathLabel, CreateChange(&changes, ObjectAdded, v3.PathLabel,
nil, g.KeyNode, false, nil, g.KeyNode, false,
nil, p.Value) nil, p.Value)
} }
} }
// wait for the things to be done. // wait for the things to be done.
completedChecks := 0 completedChecks := 0
for completedChecks < pathsChecked { for completedChecks < pathsChecked {
select { select {
case <-doneChan: case <-doneChan:
completedChecks++ completedChecks++
} }
} }
if len(pathChanges) > 0 { if len(pathChanges) > 0 {
pc.PathItemsChanges = pathChanges pc.PathItemsChanges = pathChanges
} }
pc.ExtensionChanges = CompareExtensions(lPath.Extensions, rPath.Extensions) pc.ExtensionChanges = CompareExtensions(lPath.Extensions, rPath.Extensions)
} }
// OpenAPI // OpenAPI
if reflect.TypeOf(&v3.Paths{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v3.Paths{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v3.Paths{}) == reflect.TypeOf(r) { reflect.TypeOf(&v3.Paths{}) == reflect.TypeOf(r) {
lPath := l.(*v3.Paths) lPath := l.(*v3.Paths)
rPath := r.(*v3.Paths) rPath := r.(*v3.Paths)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lPath, rPath) { if low.AreEqual(lPath, rPath) {
return nil return nil
} }
lKeys := make(map[string]low.ValueReference[*v3.PathItem]) lKeys := make(map[string]low.ValueReference[*v3.PathItem])
rKeys := make(map[string]low.ValueReference[*v3.PathItem]) rKeys := make(map[string]low.ValueReference[*v3.PathItem])
for k := range lPath.PathItems { for k := range lPath.PathItems {
lKeys[k.Value] = lPath.PathItems[k] lKeys[k.Value] = lPath.PathItems[k]
} }
for k := range rPath.PathItems { for k := range rPath.PathItems {
rKeys[k.Value] = rPath.PathItems[k] rKeys[k.Value] = rPath.PathItems[k]
} }
// run every comparison in a thread. // run every comparison in a thread.
var mLock sync.Mutex var mLock sync.Mutex
compare := func(path string, pChanges map[string]*PathItemChanges, l, r *v3.PathItem, doneChan chan bool) { compare := func(path string, pChanges map[string]*PathItemChanges, l, r *v3.PathItem, doneChan chan bool) {
if !low.AreEqual(l, r) { if !low.AreEqual(l, r) {
mLock.Lock() mLock.Lock()
pathChanges[path] = ComparePathItems(l, r) pathChanges[path] = ComparePathItems(l, r)
mLock.Unlock() mLock.Unlock()
} }
doneChan <- true doneChan <- true
} }
doneChan := make(chan bool) doneChan := make(chan bool)
pathsChecked := 0 pathsChecked := 0
for k := range lKeys { for k := range lKeys {
if _, ok := rKeys[k]; ok { if _, ok := rKeys[k]; ok {
go compare(k, pathChanges, lKeys[k].Value, rKeys[k].Value, doneChan) go compare(k, pathChanges, lKeys[k].Value, rKeys[k].Value, doneChan)
pathsChecked++ pathsChecked++
continue continue
} }
g, p := lPath.FindPathAndKey(k) g, p := lPath.FindPathAndKey(k)
CreateChange(&changes, ObjectRemoved, v3.PathLabel, CreateChange(&changes, ObjectRemoved, v3.PathLabel,
g.KeyNode, nil, true, g.KeyNode, nil, true,
p.Value, nil) p.Value, nil)
} }
for k := range rKeys { for k := range rKeys {
if _, ok := lKeys[k]; !ok { if _, ok := lKeys[k]; !ok {
g, p := rPath.FindPathAndKey(k) g, p := rPath.FindPathAndKey(k)
CreateChange(&changes, ObjectAdded, v3.PathLabel, CreateChange(&changes, ObjectAdded, v3.PathLabel,
nil, g.KeyNode, false, nil, g.KeyNode, false,
nil, p.Value) nil, p.Value)
} }
} }
// wait for the things to be done. // wait for the things to be done.
completedChecks := 0 completedChecks := 0
for completedChecks < pathsChecked { for completedChecks < pathsChecked {
select { select {
case <-doneChan: case <-doneChan:
completedChecks++ completedChecks++
} }
} }
if len(pathChanges) > 0 { if len(pathChanges) > 0 {
pc.PathItemsChanges = pathChanges pc.PathItemsChanges = pathChanges
} }
pc.ExtensionChanges = CompareExtensions(lPath.Extensions, rPath.Extensions) pc.ExtensionChanges = CompareExtensions(lPath.Extensions, rPath.Extensions)
} }
pc.Changes = changes pc.PropertyChanges = NewPropertyChanges(changes)
return pc return pc
} }

View File

@@ -4,77 +4,77 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// RequestBodyChanges represents changes made between two OpenAPI RequestBody Objects // RequestBodyChanges represents changes made between two OpenAPI RequestBody Objects
type RequestBodyChanges struct { type RequestBodyChanges struct {
PropertyChanges *PropertyChanges
ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"` ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns the total number of changes found between two OpenAPI RequestBody objects // TotalChanges returns the total number of changes found between two OpenAPI RequestBody objects
func (rb *RequestBodyChanges) TotalChanges() int { func (rb *RequestBodyChanges) TotalChanges() int {
c := rb.PropertyChanges.TotalChanges() c := rb.PropertyChanges.TotalChanges()
for k := range rb.ContentChanges { for k := range rb.ContentChanges {
c += rb.ContentChanges[k].TotalChanges() c += rb.ContentChanges[k].TotalChanges()
} }
if rb.ExtensionChanges != nil { if rb.ExtensionChanges != nil {
c += rb.ExtensionChanges.TotalChanges() c += rb.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes found between OpenAPI RequestBody objects // TotalBreakingChanges returns the total number of breaking changes found between OpenAPI RequestBody objects
func (rb *RequestBodyChanges) TotalBreakingChanges() int { func (rb *RequestBodyChanges) TotalBreakingChanges() int {
c := rb.PropertyChanges.TotalBreakingChanges() c := rb.PropertyChanges.TotalBreakingChanges()
for k := range rb.ContentChanges { for k := range rb.ContentChanges {
c += rb.ContentChanges[k].TotalBreakingChanges() c += rb.ContentChanges[k].TotalBreakingChanges()
} }
return c return c
} }
// CompareRequestBodies compares a left and right OpenAPI RequestBody object for changes. If found returns a pointer // CompareRequestBodies compares a left and right OpenAPI RequestBody object for changes. If found returns a pointer
// to a RequestBodyChanges instance. Returns nil if nothing was found. // to a RequestBodyChanges instance. Returns nil if nothing was found.
func CompareRequestBodies(l, r *v3.RequestBody) *RequestBodyChanges { func CompareRequestBodies(l, r *v3.RequestBody) *RequestBodyChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// description // description
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Description.ValueNode, LeftNode: l.Description.ValueNode,
RightNode: r.Description.ValueNode, RightNode: r.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// required // required
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Required.ValueNode, LeftNode: l.Required.ValueNode,
RightNode: r.Required.ValueNode, RightNode: r.Required.ValueNode,
Label: v3.RequiredLabel, Label: v3.RequiredLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
CheckProperties(props) CheckProperties(props)
rbc := new(RequestBodyChanges) rbc := new(RequestBodyChanges)
rbc.ContentChanges = CheckMapForChanges(l.Content.Value, r.Content.Value, rbc.ContentChanges = CheckMapForChanges(l.Content.Value, r.Content.Value,
&changes, v3.ContentLabel, CompareMediaTypes) &changes, v3.ContentLabel, CompareMediaTypes)
rbc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) rbc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
rbc.Changes = changes rbc.PropertyChanges = NewPropertyChanges(changes)
return rbc return rbc
} }

View File

@@ -4,172 +4,172 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
) )
// ResponseChanges represents changes found between two Swagger or OpenAPI Response objects. // ResponseChanges represents changes found between two Swagger or OpenAPI Response objects.
type ResponseChanges struct { type ResponseChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
HeadersChanges map[string]*HeaderChanges `json:"headers,omitempty" yaml:"headers,omitempty"` HeadersChanges map[string]*HeaderChanges `json:"headers,omitempty" yaml:"headers,omitempty"`
// Swagger Response Properties. // Swagger Response Properties.
SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"` SchemaChanges *SchemaChanges `json:"schemas,omitempty" yaml:"schemas,omitempty"`
ExamplesChanges *ExamplesChanges `json:"examples,omitempty" yaml:"examples,omitempty"` ExamplesChanges *ExamplesChanges `json:"examples,omitempty" yaml:"examples,omitempty"`
// OpenAPI Response Properties. // OpenAPI Response Properties.
ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"` ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"`
LinkChanges map[string]*LinkChanges `json:"links,omitempty" yaml:"links,omitempty"` LinkChanges map[string]*LinkChanges `json:"links,omitempty" yaml:"links,omitempty"`
ServerChanges *ServerChanges `json:"server,omitempty" yaml:"server,omitempty"` ServerChanges *ServerChanges `json:"server,omitempty" yaml:"server,omitempty"`
} }
// TotalChanges returns the total number of changes found between two Swagger or OpenAPI Response Objects // TotalChanges returns the total number of changes found between two Swagger or OpenAPI Response Objects
func (r *ResponseChanges) TotalChanges() int { func (r *ResponseChanges) TotalChanges() int {
c := r.PropertyChanges.TotalChanges() c := r.PropertyChanges.TotalChanges()
if r.ExtensionChanges != nil { if r.ExtensionChanges != nil {
c += r.ExtensionChanges.TotalChanges() c += r.ExtensionChanges.TotalChanges()
} }
if r.SchemaChanges != nil { if r.SchemaChanges != nil {
c += r.SchemaChanges.TotalChanges() c += r.SchemaChanges.TotalChanges()
} }
if r.ExamplesChanges != nil { if r.ExamplesChanges != nil {
c += r.ExamplesChanges.TotalChanges() c += r.ExamplesChanges.TotalChanges()
} }
for k := range r.HeadersChanges { for k := range r.HeadersChanges {
c += r.HeadersChanges[k].TotalChanges() c += r.HeadersChanges[k].TotalChanges()
} }
for k := range r.ContentChanges { for k := range r.ContentChanges {
c += r.ContentChanges[k].TotalChanges() c += r.ContentChanges[k].TotalChanges()
} }
for k := range r.LinkChanges { for k := range r.LinkChanges {
c += r.LinkChanges[k].TotalChanges() c += r.LinkChanges[k].TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes found between two swagger or OpenAPI // TotalBreakingChanges returns the total number of breaking changes found between two swagger or OpenAPI
// Response Objects // Response Objects
func (r *ResponseChanges) TotalBreakingChanges() int { func (r *ResponseChanges) TotalBreakingChanges() int {
c := r.PropertyChanges.TotalBreakingChanges() c := r.PropertyChanges.TotalBreakingChanges()
if r.SchemaChanges != nil { if r.SchemaChanges != nil {
c += r.SchemaChanges.TotalBreakingChanges() c += r.SchemaChanges.TotalBreakingChanges()
} }
for k := range r.HeadersChanges { for k := range r.HeadersChanges {
c += r.HeadersChanges[k].TotalBreakingChanges() c += r.HeadersChanges[k].TotalBreakingChanges()
} }
for k := range r.ContentChanges { for k := range r.ContentChanges {
c += r.ContentChanges[k].TotalBreakingChanges() c += r.ContentChanges[k].TotalBreakingChanges()
} }
for k := range r.LinkChanges { for k := range r.LinkChanges {
c += r.LinkChanges[k].TotalBreakingChanges() c += r.LinkChanges[k].TotalBreakingChanges()
} }
return c return c
} }
// CompareResponseV2 is a Swagger type safe proxy for CompareResponse // CompareResponseV2 is a Swagger type safe proxy for CompareResponse
func CompareResponseV2(l, r *v2.Response) *ResponseChanges { func CompareResponseV2(l, r *v2.Response) *ResponseChanges {
return CompareResponse(l, r) return CompareResponse(l, r)
} }
// CompareResponseV3 is an OpenAPI type safe proxy for CompareResponse // CompareResponseV3 is an OpenAPI type safe proxy for CompareResponse
func CompareResponseV3(l, r *v3.Response) *ResponseChanges { func CompareResponseV3(l, r *v3.Response) *ResponseChanges {
return CompareResponse(l, r) return CompareResponse(l, r)
} }
// CompareResponse compares a left and right Swagger or OpenAPI Response object. If anything is found // CompareResponse compares a left and right Swagger or OpenAPI Response object. If anything is found
// a pointer to a ResponseChanges is returned, otherwise it returns nil. // a pointer to a ResponseChanges is returned, otherwise it returns nil.
func CompareResponse(l, r any) *ResponseChanges { func CompareResponse(l, r any) *ResponseChanges {
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
rc := new(ResponseChanges) rc := new(ResponseChanges)
if reflect.TypeOf(&v2.Response{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Response{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v2.Response{}) == reflect.TypeOf(l) && reflect.TypeOf(&v2.Response{}) == reflect.TypeOf(r) {
lResponse := l.(*v2.Response) lResponse := l.(*v2.Response)
rResponse := r.(*v2.Response) rResponse := r.(*v2.Response)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lResponse, rResponse) { if low.AreEqual(lResponse, rResponse) {
return nil return nil
} }
// description // description
addPropertyCheck(&props, lResponse.Description.ValueNode, rResponse.Description.ValueNode, addPropertyCheck(&props, lResponse.Description.ValueNode, rResponse.Description.ValueNode,
lResponse.Description.Value, rResponse.Description.Value, &changes, v3.DescriptionLabel, false) lResponse.Description.Value, rResponse.Description.Value, &changes, v3.DescriptionLabel, false)
if !lResponse.Schema.IsEmpty() && !rResponse.Schema.IsEmpty() { if !lResponse.Schema.IsEmpty() && !rResponse.Schema.IsEmpty() {
rc.SchemaChanges = CompareSchemas(lResponse.Schema.Value, rResponse.Schema.Value) rc.SchemaChanges = CompareSchemas(lResponse.Schema.Value, rResponse.Schema.Value)
} }
if !lResponse.Schema.IsEmpty() && rResponse.Schema.IsEmpty() { if !lResponse.Schema.IsEmpty() && rResponse.Schema.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, CreateChange(&changes, ObjectRemoved, v3.SchemaLabel,
lResponse.Schema.ValueNode, nil, true, lResponse.Schema.ValueNode, nil, true,
lResponse.Schema.Value, nil) lResponse.Schema.Value, nil)
} }
if lResponse.Schema.IsEmpty() && !rResponse.Schema.IsEmpty() { if lResponse.Schema.IsEmpty() && !rResponse.Schema.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.SchemaLabel, CreateChange(&changes, ObjectAdded, v3.SchemaLabel,
nil, rResponse.Schema.ValueNode, true, nil, rResponse.Schema.ValueNode, true,
nil, rResponse.Schema.Value) nil, rResponse.Schema.Value)
} }
rc.HeadersChanges = rc.HeadersChanges =
CheckMapForChanges(lResponse.Headers.Value, rResponse.Headers.Value, CheckMapForChanges(lResponse.Headers.Value, rResponse.Headers.Value,
&changes, v3.HeadersLabel, CompareHeadersV2) &changes, v3.HeadersLabel, CompareHeadersV2)
if !lResponse.Examples.IsEmpty() && !rResponse.Examples.IsEmpty() { if !lResponse.Examples.IsEmpty() && !rResponse.Examples.IsEmpty() {
rc.ExamplesChanges = CompareExamplesV2(lResponse.Examples.Value, rResponse.Examples.Value) rc.ExamplesChanges = CompareExamplesV2(lResponse.Examples.Value, rResponse.Examples.Value)
} }
if !lResponse.Examples.IsEmpty() && rResponse.Examples.IsEmpty() { if !lResponse.Examples.IsEmpty() && rResponse.Examples.IsEmpty() {
CreateChange(&changes, PropertyRemoved, v3.ExamplesLabel, CreateChange(&changes, PropertyRemoved, v3.ExamplesLabel,
lResponse.Schema.ValueNode, nil, false, lResponse.Schema.ValueNode, nil, false,
lResponse.Schema.Value, nil) lResponse.Schema.Value, nil)
} }
if lResponse.Examples.IsEmpty() && !rResponse.Examples.IsEmpty() { if lResponse.Examples.IsEmpty() && !rResponse.Examples.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ExamplesLabel, CreateChange(&changes, ObjectAdded, v3.ExamplesLabel,
nil, rResponse.Schema.ValueNode, false, nil, rResponse.Schema.ValueNode, false,
nil, lResponse.Schema.Value) nil, lResponse.Schema.Value)
} }
rc.ExtensionChanges = CompareExtensions(lResponse.Extensions, rResponse.Extensions) rc.ExtensionChanges = CompareExtensions(lResponse.Extensions, rResponse.Extensions)
} }
if reflect.TypeOf(&v3.Response{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Response{}) == reflect.TypeOf(r) { if reflect.TypeOf(&v3.Response{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Response{}) == reflect.TypeOf(r) {
lResponse := l.(*v3.Response) lResponse := l.(*v3.Response)
rResponse := r.(*v3.Response) rResponse := r.(*v3.Response)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lResponse, rResponse) { if low.AreEqual(lResponse, rResponse) {
return nil return nil
} }
// description // description
addPropertyCheck(&props, lResponse.Description.ValueNode, rResponse.Description.ValueNode, addPropertyCheck(&props, lResponse.Description.ValueNode, rResponse.Description.ValueNode,
lResponse.Description.Value, lResponse.Description.Value, &changes, v3.DescriptionLabel, false) lResponse.Description.Value, lResponse.Description.Value, &changes, v3.DescriptionLabel, false)
rc.HeadersChanges = rc.HeadersChanges =
CheckMapForChanges(lResponse.Headers.Value, rResponse.Headers.Value, CheckMapForChanges(lResponse.Headers.Value, rResponse.Headers.Value,
&changes, v3.HeadersLabel, CompareHeadersV3) &changes, v3.HeadersLabel, CompareHeadersV3)
rc.ContentChanges = rc.ContentChanges =
CheckMapForChanges(lResponse.Content.Value, rResponse.Content.Value, CheckMapForChanges(lResponse.Content.Value, rResponse.Content.Value,
&changes, v3.ContentLabel, CompareMediaTypes) &changes, v3.ContentLabel, CompareMediaTypes)
rc.LinkChanges = rc.LinkChanges =
CheckMapForChanges(lResponse.Links.Value, rResponse.Links.Value, CheckMapForChanges(lResponse.Links.Value, rResponse.Links.Value,
&changes, v3.LinksLabel, CompareLinks) &changes, v3.LinksLabel, CompareLinks)
rc.ExtensionChanges = CompareExtensions(lResponse.Extensions, rResponse.Extensions) rc.ExtensionChanges = CompareExtensions(lResponse.Extensions, rResponse.Extensions)
} }
CheckProperties(props) CheckProperties(props)
rc.Changes = changes rc.PropertyChanges = NewPropertyChanges(changes)
if rc.TotalChanges() <= 0 { if rc.TotalChanges() <= 0 {
return nil return nil
} }
return rc return rc
} }

View File

@@ -4,121 +4,121 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
) )
// ResponsesChanges represents changes made between two Swagger or OpenAPI Responses objects. // ResponsesChanges represents changes made between two Swagger or OpenAPI Responses objects.
type ResponsesChanges struct { type ResponsesChanges struct {
PropertyChanges *PropertyChanges
ResponseChanges map[string]*ResponseChanges `json:"response,omitempty" yaml:"response,omitempty"` ResponseChanges map[string]*ResponseChanges `json:"response,omitempty" yaml:"response,omitempty"`
DefaultChanges *ResponseChanges `json:"default,omitempty" yaml:"default,omitempty"` DefaultChanges *ResponseChanges `json:"default,omitempty" yaml:"default,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns the total number of changes found between two Swagger or OpenAPI Responses objects // TotalChanges returns the total number of changes found between two Swagger or OpenAPI Responses objects
func (r *ResponsesChanges) TotalChanges() int { func (r *ResponsesChanges) TotalChanges() int {
c := r.PropertyChanges.TotalChanges() c := r.PropertyChanges.TotalChanges()
for k := range r.ResponseChanges { for k := range r.ResponseChanges {
c += r.ResponseChanges[k].TotalChanges() c += r.ResponseChanges[k].TotalChanges()
} }
if r.DefaultChanges != nil { if r.DefaultChanges != nil {
c += r.DefaultChanges.TotalChanges() c += r.DefaultChanges.TotalChanges()
} }
if r.ExtensionChanges != nil { if r.ExtensionChanges != nil {
c += r.ExtensionChanges.TotalChanges() c += r.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of changes found between two Swagger or OpenAPI // TotalBreakingChanges returns the total number of changes found between two Swagger or OpenAPI
// Responses Objects // Responses Objects
func (r *ResponsesChanges) TotalBreakingChanges() int { func (r *ResponsesChanges) TotalBreakingChanges() int {
c := r.PropertyChanges.TotalBreakingChanges() c := r.PropertyChanges.TotalBreakingChanges()
for k := range r.ResponseChanges { for k := range r.ResponseChanges {
c += r.ResponseChanges[k].TotalBreakingChanges() c += r.ResponseChanges[k].TotalBreakingChanges()
} }
if r.DefaultChanges != nil { if r.DefaultChanges != nil {
c += r.DefaultChanges.TotalBreakingChanges() c += r.DefaultChanges.TotalBreakingChanges()
} }
return c return c
} }
// CompareResponses compares a left and right Swagger or OpenAPI Responses object for any changes. If found // CompareResponses compares a left and right Swagger or OpenAPI Responses object for any changes. If found
// returns a pointer to ResponsesChanges, or returns nil. // returns a pointer to ResponsesChanges, or returns nil.
func CompareResponses(l, r any) *ResponsesChanges { func CompareResponses(l, r any) *ResponsesChanges {
var changes []*Change var changes []*Change
rc := new(ResponsesChanges) rc := new(ResponsesChanges)
// swagger // swagger
if reflect.TypeOf(&v2.Responses{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.Responses{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.Responses{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.Responses{}) == reflect.TypeOf(r) {
lResponses := l.(*v2.Responses) lResponses := l.(*v2.Responses)
rResponses := r.(*v2.Responses) rResponses := r.(*v2.Responses)
// perform hash check to avoid further processing // perform hash check to avoid further processing
if low.AreEqual(lResponses, rResponses) { if low.AreEqual(lResponses, rResponses) {
return nil return nil
} }
if !lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() { if !lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() {
rc.DefaultChanges = CompareResponse(lResponses.Default.Value, rResponses.Default.Value) rc.DefaultChanges = CompareResponse(lResponses.Default.Value, rResponses.Default.Value)
} }
if !lResponses.Default.IsEmpty() && rResponses.Default.IsEmpty() { if !lResponses.Default.IsEmpty() && rResponses.Default.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.DefaultLabel, CreateChange(&changes, ObjectRemoved, v3.DefaultLabel,
lResponses.Default.ValueNode, nil, true, lResponses.Default.ValueNode, nil, true,
lResponses.Default.Value, nil) lResponses.Default.Value, nil)
} }
if lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() { if lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.DefaultLabel, CreateChange(&changes, ObjectAdded, v3.DefaultLabel,
nil, rResponses.Default.ValueNode, false, nil, rResponses.Default.ValueNode, false,
nil, lResponses.Default.Value) nil, lResponses.Default.Value)
} }
rc.ResponseChanges = CheckMapForChanges(lResponses.Codes, rResponses.Codes, rc.ResponseChanges = CheckMapForChanges(lResponses.Codes, rResponses.Codes,
&changes, v3.CodesLabel, CompareResponseV2) &changes, v3.CodesLabel, CompareResponseV2)
rc.ExtensionChanges = CompareExtensions(lResponses.Extensions, rResponses.Extensions) rc.ExtensionChanges = CompareExtensions(lResponses.Extensions, rResponses.Extensions)
} }
// openapi // openapi
if reflect.TypeOf(&v3.Responses{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v3.Responses{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v3.Responses{}) == reflect.TypeOf(r) { reflect.TypeOf(&v3.Responses{}) == reflect.TypeOf(r) {
lResponses := l.(*v3.Responses) lResponses := l.(*v3.Responses)
rResponses := r.(*v3.Responses) rResponses := r.(*v3.Responses)
//perform hash check to avoid further processing //perform hash check to avoid further processing
if low.AreEqual(lResponses, rResponses) { if low.AreEqual(lResponses, rResponses) {
return nil return nil
} }
if !lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() { if !lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() {
rc.DefaultChanges = CompareResponse(lResponses.Default.Value, rResponses.Default.Value) rc.DefaultChanges = CompareResponse(lResponses.Default.Value, rResponses.Default.Value)
} }
if !lResponses.Default.IsEmpty() && rResponses.Default.IsEmpty() { if !lResponses.Default.IsEmpty() && rResponses.Default.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.DefaultLabel, CreateChange(&changes, ObjectRemoved, v3.DefaultLabel,
lResponses.Default.ValueNode, nil, true, lResponses.Default.ValueNode, nil, true,
lResponses.Default.Value, nil) lResponses.Default.Value, nil)
} }
if lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() { if lResponses.Default.IsEmpty() && !rResponses.Default.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.DefaultLabel, CreateChange(&changes, ObjectAdded, v3.DefaultLabel,
nil, rResponses.Default.ValueNode, false, nil, rResponses.Default.ValueNode, false,
nil, lResponses.Default.Value) nil, lResponses.Default.Value)
} }
rc.ResponseChanges = CheckMapForChanges(lResponses.Codes, rResponses.Codes, rc.ResponseChanges = CheckMapForChanges(lResponses.Codes, rResponses.Codes,
&changes, v3.CodesLabel, CompareResponseV3) &changes, v3.CodesLabel, CompareResponseV3)
rc.ExtensionChanges = CompareExtensions(lResponses.Extensions, rResponses.Extensions) rc.ExtensionChanges = CompareExtensions(lResponses.Extensions, rResponses.Extensions)
} }
rc.Changes = changes rc.PropertyChanges = NewPropertyChanges(changes)
return rc return rc
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,63 +4,63 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// ScopesChanges represents changes between two Swagger Scopes Objects // ScopesChanges represents changes between two Swagger Scopes Objects
type ScopesChanges struct { type ScopesChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns the total changes found between two Swagger Scopes objects. // TotalChanges returns the total changes found between two Swagger Scopes objects.
func (s *ScopesChanges) TotalChanges() int { func (s *ScopesChanges) TotalChanges() int {
c := s.PropertyChanges.TotalChanges() c := s.PropertyChanges.TotalChanges()
if s.ExtensionChanges != nil { if s.ExtensionChanges != nil {
c += s.ExtensionChanges.TotalChanges() c += s.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes between two Swagger Scopes objects. // TotalBreakingChanges returns the total number of breaking changes between two Swagger Scopes objects.
func (s *ScopesChanges) TotalBreakingChanges() int { func (s *ScopesChanges) TotalBreakingChanges() int {
return s.PropertyChanges.TotalBreakingChanges() return s.PropertyChanges.TotalBreakingChanges()
} }
// CompareScopes compares a left and right Swagger Scopes objects for changes. If anything is found, returns // CompareScopes compares a left and right Swagger Scopes objects for changes. If anything is found, returns
// a pointer to ScopesChanges, or returns nil if nothing is found. // a pointer to ScopesChanges, or returns nil if nothing is found.
func CompareScopes(l, r *v2.Scopes) *ScopesChanges { func CompareScopes(l, r *v2.Scopes) *ScopesChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
var changes []*Change var changes []*Change
for v := range l.Values { for v := range l.Values {
if r != nil && r.FindScope(v.Value) == nil { if r != nil && r.FindScope(v.Value) == nil {
CreateChange(&changes, ObjectRemoved, v3.Scopes, CreateChange(&changes, ObjectRemoved, v3.Scopes,
l.Values[v].ValueNode, nil, true, l.Values[v].ValueNode, nil, true,
v.Value, nil) v.Value, nil)
continue continue
} }
if r != nil && r.FindScope(v.Value) != nil { if r != nil && r.FindScope(v.Value) != nil {
if l.Values[v].Value != r.FindScope(v.Value).Value { if l.Values[v].Value != r.FindScope(v.Value).Value {
CreateChange(&changes, Modified, v3.Scopes, CreateChange(&changes, Modified, v3.Scopes,
l.Values[v].ValueNode, r.FindScope(v.Value).ValueNode, true, l.Values[v].ValueNode, r.FindScope(v.Value).ValueNode, true,
l.Values[v].Value, r.FindScope(v.Value).Value) l.Values[v].Value, r.FindScope(v.Value).Value)
} }
} }
} }
for v := range r.Values { for v := range r.Values {
if l != nil && l.FindScope(v.Value) == nil { if l != nil && l.FindScope(v.Value) == nil {
CreateChange(&changes, ObjectAdded, v3.Scopes, CreateChange(&changes, ObjectAdded, v3.Scopes,
nil, r.Values[v].ValueNode, false, nil, r.Values[v].ValueNode, false,
nil, v.Value) nil, v.Value)
} }
} }
sc := new(ScopesChanges) sc := new(ScopesChanges)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
sc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) sc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions)
return sc return sc
} }

View File

@@ -4,142 +4,142 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// SecurityRequirementChanges represents changes found between two SecurityRequirement Objects. // SecurityRequirementChanges represents changes found between two SecurityRequirement Objects.
type SecurityRequirementChanges struct { type SecurityRequirementChanges struct {
PropertyChanges *PropertyChanges
} }
// TotalChanges returns the total number of changes between two SecurityRequirement Objects. // TotalChanges returns the total number of changes between two SecurityRequirement Objects.
func (s *SecurityRequirementChanges) TotalChanges() int { func (s *SecurityRequirementChanges) TotalChanges() int {
return s.PropertyChanges.TotalChanges() return s.PropertyChanges.TotalChanges()
} }
// TotalBreakingChanges returns the total number of breaking changes between two SecurityRequirement Objects. // TotalBreakingChanges returns the total number of breaking changes between two SecurityRequirement Objects.
func (s *SecurityRequirementChanges) TotalBreakingChanges() int { func (s *SecurityRequirementChanges) TotalBreakingChanges() int {
return s.PropertyChanges.TotalBreakingChanges() return s.PropertyChanges.TotalBreakingChanges()
} }
// CompareSecurityRequirement compares left and right SecurityRequirement objects for changes. If anything // CompareSecurityRequirement compares left and right SecurityRequirement objects for changes. If anything
// is found, then a pointer to SecurityRequirementChanges is returned, otherwise nil. // is found, then a pointer to SecurityRequirementChanges is returned, otherwise nil.
func CompareSecurityRequirement(l, r *base.SecurityRequirement) *SecurityRequirementChanges { func CompareSecurityRequirement(l, r *base.SecurityRequirement) *SecurityRequirementChanges {
var changes []*Change var changes []*Change
sc := new(SecurityRequirementChanges) sc := new(SecurityRequirementChanges)
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
checkSecurityRequirement(l.Requirements.Value, r.Requirements.Value, &changes) checkSecurityRequirement(l.Requirements.Value, r.Requirements.Value, &changes)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
return sc return sc
} }
func removedSecurityRequirement(vn *yaml.Node, name string, changes *[]*Change) { func removedSecurityRequirement(vn *yaml.Node, name string, changes *[]*Change) {
CreateChange(changes, ObjectRemoved, v3.SecurityLabel, CreateChange(changes, ObjectRemoved, v3.SecurityLabel,
vn, nil, true, name, nil) vn, nil, true, name, nil)
} }
func addedSecurityRequirement(vn *yaml.Node, name string, changes *[]*Change) { func addedSecurityRequirement(vn *yaml.Node, name string, changes *[]*Change) {
CreateChange(changes, ObjectAdded, v3.SecurityLabel, CreateChange(changes, ObjectAdded, v3.SecurityLabel,
nil, vn, false, nil, name) nil, vn, false, nil, name)
} }
// tricky to do this correctly, this is my solution. // tricky to do this correctly, this is my solution.
func checkSecurityRequirement(lSec, rSec map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]], func checkSecurityRequirement(lSec, rSec map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]],
changes *[]*Change) { changes *[]*Change) {
lKeys := make([]string, len(lSec)) lKeys := make([]string, len(lSec))
rKeys := make([]string, len(rSec)) rKeys := make([]string, len(rSec))
lValues := make(map[string]low.ValueReference[[]low.ValueReference[string]]) lValues := make(map[string]low.ValueReference[[]low.ValueReference[string]])
rValues := make(map[string]low.ValueReference[[]low.ValueReference[string]]) rValues := make(map[string]low.ValueReference[[]low.ValueReference[string]])
var n, z int var n, z int
for i := range lSec { for i := range lSec {
lKeys[n] = i.Value lKeys[n] = i.Value
lValues[i.Value] = lSec[i] lValues[i.Value] = lSec[i]
n++ n++
} }
for i := range rSec { for i := range rSec {
rKeys[z] = i.Value rKeys[z] = i.Value
rValues[i.Value] = rSec[i] rValues[i.Value] = rSec[i]
z++ z++
} }
for z = range lKeys { for z = range lKeys {
if z < len(rKeys) { if z < len(rKeys) {
if _, ok := rValues[lKeys[z]]; !ok { if _, ok := rValues[lKeys[z]]; !ok {
removedSecurityRequirement(lValues[lKeys[z]].ValueNode, lKeys[z], changes) removedSecurityRequirement(lValues[lKeys[z]].ValueNode, lKeys[z], changes)
continue continue
} }
lValue := lValues[lKeys[z]].Value lValue := lValues[lKeys[z]].Value
rValue := rValues[lKeys[z]].Value rValue := rValues[lKeys[z]].Value
// check if actual values match up // check if actual values match up
lRoleKeys := make([]string, len(lValue)) lRoleKeys := make([]string, len(lValue))
rRoleKeys := make([]string, len(rValue)) rRoleKeys := make([]string, len(rValue))
lRoleValues := make(map[string]low.ValueReference[string]) lRoleValues := make(map[string]low.ValueReference[string])
rRoleValues := make(map[string]low.ValueReference[string]) rRoleValues := make(map[string]low.ValueReference[string])
var t, k int var t, k int
for i := range lValue { for i := range lValue {
lRoleKeys[t] = lValue[i].Value lRoleKeys[t] = lValue[i].Value
lRoleValues[lValue[i].Value] = lValue[i] lRoleValues[lValue[i].Value] = lValue[i]
t++ t++
} }
for i := range rValue { for i := range rValue {
rRoleKeys[k] = rValue[i].Value rRoleKeys[k] = rValue[i].Value
rRoleValues[rValue[i].Value] = rValue[i] rRoleValues[rValue[i].Value] = rValue[i]
k++ k++
} }
for t = range lRoleKeys { for t = range lRoleKeys {
if t < len(rRoleKeys) { if t < len(rRoleKeys) {
if _, ok := rRoleValues[lRoleKeys[t]]; !ok { if _, ok := rRoleValues[lRoleKeys[t]]; !ok {
removedSecurityRequirement(lRoleValues[lRoleKeys[t]].ValueNode, lRoleKeys[t], changes) removedSecurityRequirement(lRoleValues[lRoleKeys[t]].ValueNode, lRoleKeys[t], changes)
continue continue
} }
} }
if t >= len(rRoleKeys) { if t >= len(rRoleKeys) {
if _, ok := rRoleValues[lRoleKeys[t]]; !ok { if _, ok := rRoleValues[lRoleKeys[t]]; !ok {
removedSecurityRequirement(lRoleValues[lRoleKeys[t]].ValueNode, lRoleKeys[t], changes) removedSecurityRequirement(lRoleValues[lRoleKeys[t]].ValueNode, lRoleKeys[t], changes)
} }
} }
} }
for t = range rRoleKeys { for t = range rRoleKeys {
if t < len(lRoleKeys) { if t < len(lRoleKeys) {
if _, ok := lRoleValues[rRoleKeys[t]]; !ok { if _, ok := lRoleValues[rRoleKeys[t]]; !ok {
addedSecurityRequirement(rRoleValues[rRoleKeys[t]].ValueNode, rRoleKeys[t], changes) addedSecurityRequirement(rRoleValues[rRoleKeys[t]].ValueNode, rRoleKeys[t], changes)
continue continue
} }
} }
if t >= len(lRoleKeys) { if t >= len(lRoleKeys) {
addedSecurityRequirement(rRoleValues[rRoleKeys[t]].ValueNode, rRoleKeys[t], changes) addedSecurityRequirement(rRoleValues[rRoleKeys[t]].ValueNode, rRoleKeys[t], changes)
} }
} }
} }
if z >= len(rKeys) { if z >= len(rKeys) {
if _, ok := rValues[lKeys[z]]; !ok { if _, ok := rValues[lKeys[z]]; !ok {
removedSecurityRequirement(lValues[lKeys[z]].ValueNode, lKeys[z], changes) removedSecurityRequirement(lValues[lKeys[z]].ValueNode, lKeys[z], changes)
} }
} }
} }
for z = range rKeys { for z = range rKeys {
if z < len(lKeys) { if z < len(lKeys) {
if _, ok := lValues[rKeys[z]]; !ok { if _, ok := lValues[rKeys[z]]; !ok {
addedSecurityRequirement(rValues[rKeys[z]].ValueNode, rKeys[z], changes) addedSecurityRequirement(rValues[rKeys[z]].ValueNode, rKeys[z], changes)
continue continue
} }
} }
if z >= len(lKeys) { if z >= len(lKeys) {
if _, ok := lValues[rKeys[z]]; !ok { if _, ok := lValues[rKeys[z]]; !ok {
addedSecurityRequirement(rValues[rKeys[z]].ValueNode, rKeys[z], changes) addedSecurityRequirement(rValues[rKeys[z]].ValueNode, rKeys[z], changes)
} }
} }
} }
} }

View File

@@ -4,162 +4,162 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect" "reflect"
) )
// SecuritySchemeChanges represents changes made between Swagger or OpenAPI SecurityScheme Objects. // SecuritySchemeChanges represents changes made between Swagger or OpenAPI SecurityScheme Objects.
type SecuritySchemeChanges struct { type SecuritySchemeChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
// OpenAPI Version // OpenAPI Version
OAuthFlowChanges *OAuthFlowsChanges `json:"oAuthFlow,omitempty" yaml:"oAuthFlow,omitempty"` OAuthFlowChanges *OAuthFlowsChanges `json:"oAuthFlow,omitempty" yaml:"oAuthFlow,omitempty"`
// Swagger Version // Swagger Version
ScopesChanges *ScopesChanges `json:"scopes,omitempty" yaml:"scopes,omitempty"` ScopesChanges *ScopesChanges `json:"scopes,omitempty" yaml:"scopes,omitempty"`
} }
// TotalChanges represents total changes found between two Swagger or OpenAPI SecurityScheme instances. // TotalChanges represents total changes found between two Swagger or OpenAPI SecurityScheme instances.
func (ss *SecuritySchemeChanges) TotalChanges() int { func (ss *SecuritySchemeChanges) TotalChanges() int {
c := ss.PropertyChanges.TotalChanges() c := ss.PropertyChanges.TotalChanges()
if ss.OAuthFlowChanges != nil { if ss.OAuthFlowChanges != nil {
c += ss.OAuthFlowChanges.TotalChanges() c += ss.OAuthFlowChanges.TotalChanges()
} }
if ss.ScopesChanges != nil { if ss.ScopesChanges != nil {
c += ss.ScopesChanges.TotalChanges() c += ss.ScopesChanges.TotalChanges()
} }
if ss.ExtensionChanges != nil { if ss.ExtensionChanges != nil {
c += ss.ExtensionChanges.TotalChanges() c += ss.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns total number of breaking changes between two SecurityScheme Objects. // TotalBreakingChanges returns total number of breaking changes between two SecurityScheme Objects.
func (ss *SecuritySchemeChanges) TotalBreakingChanges() int { func (ss *SecuritySchemeChanges) TotalBreakingChanges() int {
c := ss.PropertyChanges.TotalBreakingChanges() c := ss.PropertyChanges.TotalBreakingChanges()
if ss.OAuthFlowChanges != nil { if ss.OAuthFlowChanges != nil {
c += ss.OAuthFlowChanges.TotalBreakingChanges() c += ss.OAuthFlowChanges.TotalBreakingChanges()
} }
if ss.ScopesChanges != nil { if ss.ScopesChanges != nil {
c += ss.ScopesChanges.TotalBreakingChanges() c += ss.ScopesChanges.TotalBreakingChanges()
} }
return c return c
} }
// CompareSecuritySchemesV2 is a Swagger type safe proxy for CompareSecuritySchemes // CompareSecuritySchemesV2 is a Swagger type safe proxy for CompareSecuritySchemes
func CompareSecuritySchemesV2(l, r *v2.SecurityScheme) *SecuritySchemeChanges { func CompareSecuritySchemesV2(l, r *v2.SecurityScheme) *SecuritySchemeChanges {
return CompareSecuritySchemes(l, r) return CompareSecuritySchemes(l, r)
} }
// CompareSecuritySchemesV3 is an OpenAPI type safe proxt for CompareSecuritySchemes // CompareSecuritySchemesV3 is an OpenAPI type safe proxt for CompareSecuritySchemes
func CompareSecuritySchemesV3(l, r *v3.SecurityScheme) *SecuritySchemeChanges { func CompareSecuritySchemesV3(l, r *v3.SecurityScheme) *SecuritySchemeChanges {
return CompareSecuritySchemes(l, r) return CompareSecuritySchemes(l, r)
} }
// CompareSecuritySchemes compares left and right Swagger or OpenAPI Security Scheme objects for changes. // CompareSecuritySchemes compares left and right Swagger or OpenAPI Security Scheme objects for changes.
// If anything is found, returns a pointer to *SecuritySchemeChanges or nil if nothing is found. // If anything is found, returns a pointer to *SecuritySchemeChanges or nil if nothing is found.
func CompareSecuritySchemes(l, r any) *SecuritySchemeChanges { func CompareSecuritySchemes(l, r any) *SecuritySchemeChanges {
var props []*PropertyCheck var props []*PropertyCheck
var changes []*Change var changes []*Change
sc := new(SecuritySchemeChanges) sc := new(SecuritySchemeChanges)
if reflect.TypeOf(&v2.SecurityScheme{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v2.SecurityScheme{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.SecurityScheme{}) == reflect.TypeOf(r) { reflect.TypeOf(&v2.SecurityScheme{}) == reflect.TypeOf(r) {
lSS := l.(*v2.SecurityScheme) lSS := l.(*v2.SecurityScheme)
rSS := r.(*v2.SecurityScheme) rSS := r.(*v2.SecurityScheme)
if low.AreEqual(lSS, rSS) { if low.AreEqual(lSS, rSS) {
return nil return nil
} }
addPropertyCheck(&props, lSS.Type.ValueNode, rSS.Type.ValueNode, addPropertyCheck(&props, lSS.Type.ValueNode, rSS.Type.ValueNode,
lSS.Type.Value, rSS.Type.Value, &changes, v3.TypeLabel, true) lSS.Type.Value, rSS.Type.Value, &changes, v3.TypeLabel, true)
addPropertyCheck(&props, lSS.Description.ValueNode, rSS.Description.ValueNode, addPropertyCheck(&props, lSS.Description.ValueNode, rSS.Description.ValueNode,
lSS.Description.Value, rSS.Description.Value, &changes, v3.DescriptionLabel, false) lSS.Description.Value, rSS.Description.Value, &changes, v3.DescriptionLabel, false)
addPropertyCheck(&props, lSS.Name.ValueNode, rSS.Name.ValueNode, addPropertyCheck(&props, lSS.Name.ValueNode, rSS.Name.ValueNode,
lSS.Name.Value, rSS.Name.Value, &changes, v3.NameLabel, true) lSS.Name.Value, rSS.Name.Value, &changes, v3.NameLabel, true)
addPropertyCheck(&props, lSS.In.ValueNode, rSS.In.ValueNode, addPropertyCheck(&props, lSS.In.ValueNode, rSS.In.ValueNode,
lSS.In.Value, rSS.In.Value, &changes, v3.InLabel, true) lSS.In.Value, rSS.In.Value, &changes, v3.InLabel, true)
addPropertyCheck(&props, lSS.Flow.ValueNode, rSS.Flow.ValueNode, addPropertyCheck(&props, lSS.Flow.ValueNode, rSS.Flow.ValueNode,
lSS.Flow.Value, rSS.Flow.Value, &changes, v3.FlowLabel, true) lSS.Flow.Value, rSS.Flow.Value, &changes, v3.FlowLabel, true)
addPropertyCheck(&props, lSS.AuthorizationUrl.ValueNode, rSS.AuthorizationUrl.ValueNode, addPropertyCheck(&props, lSS.AuthorizationUrl.ValueNode, rSS.AuthorizationUrl.ValueNode,
lSS.AuthorizationUrl.Value, rSS.AuthorizationUrl.Value, &changes, v3.AuthorizationUrlLabel, true) lSS.AuthorizationUrl.Value, rSS.AuthorizationUrl.Value, &changes, v3.AuthorizationUrlLabel, true)
addPropertyCheck(&props, lSS.TokenUrl.ValueNode, rSS.TokenUrl.ValueNode, addPropertyCheck(&props, lSS.TokenUrl.ValueNode, rSS.TokenUrl.ValueNode,
lSS.TokenUrl.Value, rSS.TokenUrl.Value, &changes, v3.TokenUrlLabel, true) lSS.TokenUrl.Value, rSS.TokenUrl.Value, &changes, v3.TokenUrlLabel, true)
if !lSS.Scopes.IsEmpty() && !rSS.Scopes.IsEmpty() { if !lSS.Scopes.IsEmpty() && !rSS.Scopes.IsEmpty() {
if !low.AreEqual(lSS.Scopes.Value, rSS.Scopes.Value) { if !low.AreEqual(lSS.Scopes.Value, rSS.Scopes.Value) {
sc.ScopesChanges = CompareScopes(lSS.Scopes.Value, rSS.Scopes.Value) sc.ScopesChanges = CompareScopes(lSS.Scopes.Value, rSS.Scopes.Value)
} }
} }
if lSS.Scopes.IsEmpty() && !rSS.Scopes.IsEmpty() { if lSS.Scopes.IsEmpty() && !rSS.Scopes.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ScopesLabel, CreateChange(&changes, ObjectAdded, v3.ScopesLabel,
nil, rSS.Scopes.ValueNode, false, nil, rSS.Scopes.Value) nil, rSS.Scopes.ValueNode, false, nil, rSS.Scopes.Value)
} }
if !lSS.Scopes.IsEmpty() && rSS.Scopes.IsEmpty() { if !lSS.Scopes.IsEmpty() && rSS.Scopes.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.ScopesLabel, CreateChange(&changes, ObjectRemoved, v3.ScopesLabel,
lSS.Scopes.ValueNode, nil, true, lSS.Scopes.Value, nil) lSS.Scopes.ValueNode, nil, true, lSS.Scopes.Value, nil)
} }
sc.ExtensionChanges = CompareExtensions(lSS.Extensions, rSS.Extensions) sc.ExtensionChanges = CompareExtensions(lSS.Extensions, rSS.Extensions)
} }
if reflect.TypeOf(&v3.SecurityScheme{}) == reflect.TypeOf(l) && if reflect.TypeOf(&v3.SecurityScheme{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v3.SecurityScheme{}) == reflect.TypeOf(r) { reflect.TypeOf(&v3.SecurityScheme{}) == reflect.TypeOf(r) {
lSS := l.(*v3.SecurityScheme) lSS := l.(*v3.SecurityScheme)
rSS := r.(*v3.SecurityScheme) rSS := r.(*v3.SecurityScheme)
if low.AreEqual(lSS, rSS) { if low.AreEqual(lSS, rSS) {
return nil return nil
} }
addPropertyCheck(&props, lSS.Type.ValueNode, rSS.Type.ValueNode, addPropertyCheck(&props, lSS.Type.ValueNode, rSS.Type.ValueNode,
lSS.Type.Value, rSS.Type.Value, &changes, v3.TypeLabel, true) lSS.Type.Value, rSS.Type.Value, &changes, v3.TypeLabel, true)
addPropertyCheck(&props, lSS.Description.ValueNode, rSS.Description.ValueNode, addPropertyCheck(&props, lSS.Description.ValueNode, rSS.Description.ValueNode,
lSS.Description.Value, rSS.Description.Value, &changes, v3.DescriptionLabel, false) lSS.Description.Value, rSS.Description.Value, &changes, v3.DescriptionLabel, false)
addPropertyCheck(&props, lSS.Name.ValueNode, rSS.Name.ValueNode, addPropertyCheck(&props, lSS.Name.ValueNode, rSS.Name.ValueNode,
lSS.Name.Value, rSS.Name.Value, &changes, v3.NameLabel, true) lSS.Name.Value, rSS.Name.Value, &changes, v3.NameLabel, true)
addPropertyCheck(&props, lSS.In.ValueNode, rSS.In.ValueNode, addPropertyCheck(&props, lSS.In.ValueNode, rSS.In.ValueNode,
lSS.In.Value, rSS.In.Value, &changes, v3.InLabel, true) lSS.In.Value, rSS.In.Value, &changes, v3.InLabel, true)
addPropertyCheck(&props, lSS.Scheme.ValueNode, rSS.Scheme.ValueNode, addPropertyCheck(&props, lSS.Scheme.ValueNode, rSS.Scheme.ValueNode,
lSS.Scheme.Value, rSS.Scheme.Value, &changes, v3.SchemeLabel, true) lSS.Scheme.Value, rSS.Scheme.Value, &changes, v3.SchemeLabel, true)
addPropertyCheck(&props, lSS.BearerFormat.ValueNode, rSS.BearerFormat.ValueNode, addPropertyCheck(&props, lSS.BearerFormat.ValueNode, rSS.BearerFormat.ValueNode,
lSS.BearerFormat.Value, rSS.BearerFormat.Value, &changes, v3.SchemeLabel, false) lSS.BearerFormat.Value, rSS.BearerFormat.Value, &changes, v3.SchemeLabel, false)
addPropertyCheck(&props, lSS.OpenIdConnectUrl.ValueNode, rSS.OpenIdConnectUrl.ValueNode, addPropertyCheck(&props, lSS.OpenIdConnectUrl.ValueNode, rSS.OpenIdConnectUrl.ValueNode,
lSS.OpenIdConnectUrl.Value, rSS.OpenIdConnectUrl.Value, &changes, v3.OpenIdConnectUrlLabel, false) lSS.OpenIdConnectUrl.Value, rSS.OpenIdConnectUrl.Value, &changes, v3.OpenIdConnectUrlLabel, false)
if !lSS.Flows.IsEmpty() && !rSS.Flows.IsEmpty() { if !lSS.Flows.IsEmpty() && !rSS.Flows.IsEmpty() {
if !low.AreEqual(lSS.Flows.Value, rSS.Flows.Value) { if !low.AreEqual(lSS.Flows.Value, rSS.Flows.Value) {
sc.OAuthFlowChanges = CompareOAuthFlows(lSS.Flows.Value, rSS.Flows.Value) sc.OAuthFlowChanges = CompareOAuthFlows(lSS.Flows.Value, rSS.Flows.Value)
} }
} }
if lSS.Flows.IsEmpty() && !rSS.Flows.IsEmpty() { if lSS.Flows.IsEmpty() && !rSS.Flows.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.FlowsLabel, CreateChange(&changes, ObjectAdded, v3.FlowsLabel,
nil, rSS.Flows.ValueNode, false, nil, rSS.Flows.Value) nil, rSS.Flows.ValueNode, false, nil, rSS.Flows.Value)
} }
if !lSS.Flows.IsEmpty() && rSS.Flows.IsEmpty() { if !lSS.Flows.IsEmpty() && rSS.Flows.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.ScopesLabel, CreateChange(&changes, ObjectRemoved, v3.ScopesLabel,
lSS.Flows.ValueNode, nil, true, lSS.Flows.Value, nil) lSS.Flows.ValueNode, nil, true, lSS.Flows.Value, nil)
} }
sc.ExtensionChanges = CompareExtensions(lSS.Extensions, rSS.Extensions) sc.ExtensionChanges = CompareExtensions(lSS.Extensions, rSS.Extensions)
} }
CheckProperties(props) CheckProperties(props)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
return sc return sc
} }

View File

@@ -4,69 +4,69 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// ServerChanges represents changes found between two OpenAPI Server Objects // ServerChanges represents changes found between two OpenAPI Server Objects
type ServerChanges struct { type ServerChanges struct {
PropertyChanges *PropertyChanges
ServerVariableChanges map[string]*ServerVariableChanges `json:"serverVariables,omitempty" yaml:"serverVariables,omitempty"` ServerVariableChanges map[string]*ServerVariableChanges `json:"serverVariables,omitempty" yaml:"serverVariables,omitempty"`
} }
// TotalChanges returns total changes found between two OpenAPI Server Objects // TotalChanges returns total changes found between two OpenAPI Server Objects
func (s *ServerChanges) TotalChanges() int { func (s *ServerChanges) TotalChanges() int {
c := s.PropertyChanges.TotalChanges() c := s.PropertyChanges.TotalChanges()
for k := range s.ServerVariableChanges { for k := range s.ServerVariableChanges {
c += s.ServerVariableChanges[k].TotalChanges() c += s.ServerVariableChanges[k].TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the total number of breaking changes found between two OpenAPI Server objects. // TotalBreakingChanges returns the total number of breaking changes found between two OpenAPI Server objects.
func (s *ServerChanges) TotalBreakingChanges() int { func (s *ServerChanges) TotalBreakingChanges() int {
c := s.PropertyChanges.TotalBreakingChanges() c := s.PropertyChanges.TotalBreakingChanges()
for k := range s.ServerVariableChanges { for k := range s.ServerVariableChanges {
c += s.ServerVariableChanges[k].TotalBreakingChanges() c += s.ServerVariableChanges[k].TotalBreakingChanges()
} }
return c return c
} }
// CompareServers compares two OpenAPI Server objects for any changes. If anything is found, returns a pointer // CompareServers compares two OpenAPI Server objects for any changes. If anything is found, returns a pointer
// to a ServerChanges instance, or returns nil if nothing is found. // to a ServerChanges instance, or returns nil if nothing is found.
func CompareServers(l, r *v3.Server) *ServerChanges { func CompareServers(l, r *v3.Server) *ServerChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// URL // URL
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.URL.ValueNode, LeftNode: l.URL.ValueNode,
RightNode: r.URL.ValueNode, RightNode: r.URL.ValueNode,
Label: v3.URLLabel, Label: v3.URLLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// Description // Description
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Description.ValueNode, LeftNode: l.Description.ValueNode,
RightNode: r.Description.ValueNode, RightNode: r.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
CheckProperties(props) CheckProperties(props)
sc := new(ServerChanges) sc := new(ServerChanges)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
sc.ServerVariableChanges = CheckMapForChanges(l.Variables.Value, r.Variables.Value, sc.ServerVariableChanges = CheckMapForChanges(l.Variables.Value, r.Variables.Value,
&changes, v3.VariablesLabel, CompareServerVariables) &changes, v3.VariablesLabel, CompareServerVariables)
return sc return sc
} }

View File

@@ -4,74 +4,74 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// ServerVariableChanges represents changes found between two OpenAPI ServerVariable Objects // ServerVariableChanges represents changes found between two OpenAPI ServerVariable Objects
type ServerVariableChanges struct { type ServerVariableChanges struct {
PropertyChanges *PropertyChanges
} }
// CompareServerVariables compares a left and right OpenAPI ServerVariable object for changes. // CompareServerVariables compares a left and right OpenAPI ServerVariable object for changes.
// If anything is found, returns a pointer to a ServerVariableChanges instance, otherwise returns nil. // If anything is found, returns a pointer to a ServerVariableChanges instance, otherwise returns nil.
func CompareServerVariables(l, r *v3.ServerVariable) *ServerVariableChanges { func CompareServerVariables(l, r *v3.ServerVariable) *ServerVariableChanges {
if low.AreEqual(l, r) { if low.AreEqual(l, r) {
return nil return nil
} }
var props []*PropertyCheck var props []*PropertyCheck
var changes []*Change var changes []*Change
lValues := make(map[string]low.NodeReference[string]) lValues := make(map[string]low.NodeReference[string])
rValues := make(map[string]low.NodeReference[string]) rValues := make(map[string]low.NodeReference[string])
for i := range l.Enum { for i := range l.Enum {
lValues[l.Enum[i].Value] = l.Enum[i] lValues[l.Enum[i].Value] = l.Enum[i]
} }
for i := range r.Enum { for i := range r.Enum {
rValues[r.Enum[i].Value] = r.Enum[i] rValues[r.Enum[i].Value] = r.Enum[i]
} }
for k := range lValues { for k := range lValues {
if _, ok := rValues[k]; !ok { if _, ok := rValues[k]; !ok {
CreateChange(&changes, ObjectRemoved, v3.EnumLabel, CreateChange(&changes, ObjectRemoved, v3.EnumLabel,
lValues[k].ValueNode, nil, true, lValues[k].ValueNode, nil, true,
lValues[k].Value, nil) lValues[k].Value, nil)
continue continue
} }
} }
for k := range rValues { for k := range rValues {
if _, ok := lValues[k]; !ok { if _, ok := lValues[k]; !ok {
CreateChange(&changes, ObjectAdded, v3.EnumLabel, CreateChange(&changes, ObjectAdded, v3.EnumLabel,
lValues[k].ValueNode, rValues[k].ValueNode, false, lValues[k].ValueNode, rValues[k].ValueNode, false,
lValues[k].Value, rValues[k].Value) lValues[k].Value, rValues[k].Value)
} }
} }
// default // default
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Default.ValueNode, LeftNode: l.Default.ValueNode,
RightNode: r.Default.ValueNode, RightNode: r.Default.ValueNode,
Label: v3.DefaultLabel, Label: v3.DefaultLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// description // description
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Description.ValueNode, LeftNode: l.Description.ValueNode,
RightNode: r.Description.ValueNode, RightNode: r.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: l, Original: l,
New: r, New: r,
}) })
// check everything. // check everything.
CheckProperties(props) CheckProperties(props)
sc := new(ServerVariableChanges) sc := new(ServerVariableChanges)
sc.Changes = changes sc.PropertyChanges = NewPropertyChanges(changes)
return sc return sc
} }

View File

@@ -4,33 +4,33 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// TagChanges represents changes made to the Tags object of an OpenAPI document. // TagChanges represents changes made to the Tags object of an OpenAPI document.
type TagChanges struct { type TagChanges struct {
PropertyChanges *PropertyChanges
ExternalDocs *ExternalDocChanges `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` ExternalDocs *ExternalDocChanges `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns a count of everything that changed within tags. // TotalChanges returns a count of everything that changed within tags.
func (t *TagChanges) TotalChanges() int { func (t *TagChanges) TotalChanges() int {
c := t.PropertyChanges.TotalChanges() c := t.PropertyChanges.TotalChanges()
if t.ExternalDocs != nil { if t.ExternalDocs != nil {
c += t.ExternalDocs.TotalChanges() c += t.ExternalDocs.TotalChanges()
} }
if t.ExtensionChanges != nil { if t.ExtensionChanges != nil {
c += t.ExtensionChanges.TotalChanges() c += t.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the number of breaking changes made by Tags // TotalBreakingChanges returns the number of breaking changes made by Tags
func (t *TagChanges) TotalBreakingChanges() int { func (t *TagChanges) TotalBreakingChanges() int {
return t.PropertyChanges.TotalBreakingChanges() return t.PropertyChanges.TotalBreakingChanges()
} }
// CompareTags will compare a left (original) and a right (new) slice of ValueReference nodes for // CompareTags will compare a left (original) and a right (new) slice of ValueReference nodes for
@@ -38,100 +38,100 @@ func (t *TagChanges) TotalBreakingChanges() int {
// nil is returned instead. // nil is returned instead.
func CompareTags(l, r []low.ValueReference[*base.Tag]) []*TagChanges { func CompareTags(l, r []low.ValueReference[*base.Tag]) []*TagChanges {
var tagResults []*TagChanges var tagResults []*TagChanges
// look at the original and then look through the new. // look at the original and then look through the new.
seenLeft := make(map[string]*low.ValueReference[*base.Tag]) seenLeft := make(map[string]*low.ValueReference[*base.Tag])
seenRight := make(map[string]*low.ValueReference[*base.Tag]) seenRight := make(map[string]*low.ValueReference[*base.Tag])
for i := range l { for i := range l {
h := l[i] h := l[i]
seenLeft[l[i].Value.Name.Value] = &h seenLeft[l[i].Value.Name.Value] = &h
} }
for i := range r { for i := range r {
h := r[i] h := r[i]
seenRight[r[i].Value.Name.Value] = &h seenRight[r[i].Value.Name.Value] = &h
} }
//var changes []*Change //var changes []*Change
// check for removals, modifications and moves // check for removals, modifications and moves
for i := range seenLeft { for i := range seenLeft {
tc := new(TagChanges) tc := new(TagChanges)
var changes []*Change var changes []*Change
CheckForObjectAdditionOrRemoval[*base.Tag](seenLeft, seenRight, i, &changes, false, true) CheckForObjectAdditionOrRemoval[*base.Tag](seenLeft, seenRight, i, &changes, false, true)
// if the existing tag exists, let's check it. // if the existing tag exists, let's check it.
if seenRight[i] != nil { if seenRight[i] != nil {
var props []*PropertyCheck var props []*PropertyCheck
// Name // Name
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: seenLeft[i].Value.Name.ValueNode, LeftNode: seenLeft[i].Value.Name.ValueNode,
RightNode: seenRight[i].Value.Name.ValueNode, RightNode: seenRight[i].Value.Name.ValueNode,
Label: v3.NameLabel, Label: v3.NameLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: seenLeft[i].Value, Original: seenLeft[i].Value,
New: seenRight[i].Value, New: seenRight[i].Value,
}) })
// Description // Description
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: seenLeft[i].Value.Description.ValueNode, LeftNode: seenLeft[i].Value.Description.ValueNode,
RightNode: seenRight[i].Value.Description.ValueNode, RightNode: seenRight[i].Value.Description.ValueNode,
Label: v3.DescriptionLabel, Label: v3.DescriptionLabel,
Changes: &changes, Changes: &changes,
Breaking: false, Breaking: false,
Original: seenLeft[i].Value, Original: seenLeft[i].Value,
New: seenRight[i].Value, New: seenRight[i].Value,
}) })
// check properties // check properties
CheckProperties(props) CheckProperties(props)
// compare external docs // compare external docs
if !seenLeft[i].Value.ExternalDocs.IsEmpty() && !seenRight[i].Value.ExternalDocs.IsEmpty() { if !seenLeft[i].Value.ExternalDocs.IsEmpty() && !seenRight[i].Value.ExternalDocs.IsEmpty() {
tc.ExternalDocs = CompareExternalDocs(seenLeft[i].Value.ExternalDocs.Value, tc.ExternalDocs = CompareExternalDocs(seenLeft[i].Value.ExternalDocs.Value,
seenRight[i].Value.ExternalDocs.Value) seenRight[i].Value.ExternalDocs.Value)
} }
if seenLeft[i].Value.ExternalDocs.IsEmpty() && !seenRight[i].Value.ExternalDocs.IsEmpty() { if seenLeft[i].Value.ExternalDocs.IsEmpty() && !seenRight[i].Value.ExternalDocs.IsEmpty() {
CreateChange(&changes, ObjectAdded, v3.ExternalDocsLabel, nil, seenRight[i].GetValueNode(), CreateChange(&changes, ObjectAdded, v3.ExternalDocsLabel, nil, seenRight[i].GetValueNode(),
false, nil, seenRight[i].Value.ExternalDocs.Value) false, nil, seenRight[i].Value.ExternalDocs.Value)
} }
if !seenLeft[i].Value.ExternalDocs.IsEmpty() && seenRight[i].Value.ExternalDocs.IsEmpty() { if !seenLeft[i].Value.ExternalDocs.IsEmpty() && seenRight[i].Value.ExternalDocs.IsEmpty() {
CreateChange(&changes, ObjectRemoved, v3.ExternalDocsLabel, seenLeft[i].GetValueNode(), nil, CreateChange(&changes, ObjectRemoved, v3.ExternalDocsLabel, seenLeft[i].GetValueNode(), nil,
false, seenLeft[i].Value.ExternalDocs.Value, nil) false, seenLeft[i].Value.ExternalDocs.Value, nil)
} }
// check extensions // check extensions
tc.ExtensionChanges = CompareExtensions(seenLeft[i].Value.Extensions, seenRight[i].Value.Extensions) tc.ExtensionChanges = CompareExtensions(seenLeft[i].Value.Extensions, seenRight[i].Value.Extensions)
tc.Changes = changes tc.PropertyChanges = NewPropertyChanges(changes)
if tc.TotalChanges() > 0 { if tc.TotalChanges() > 0 {
tagResults = append(tagResults, tc) tagResults = append(tagResults, tc)
} }
continue continue
} }
if len(changes) > 0 { if len(changes) > 0 {
tc.Changes = changes tc.PropertyChanges = NewPropertyChanges(changes)
tagResults = append(tagResults, tc) tagResults = append(tagResults, tc)
} }
} }
for i := range seenRight { for i := range seenRight {
if seenLeft[i] == nil { if seenLeft[i] == nil {
tc := new(TagChanges) tc := new(TagChanges)
var changes []*Change var changes []*Change
CreateChange(&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(), CreateChange(&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(),
false, nil, seenRight[i].GetValue()) false, nil, seenRight[i].GetValue())
tc.Changes = changes tc.PropertyChanges = NewPropertyChanges(changes)
tagResults = append(tagResults, tc) tagResults = append(tagResults, tc)
} }
} }
return tagResults return tagResults
} }

View File

@@ -4,101 +4,101 @@
package model package model
import ( import (
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// XMLChanges represents changes made to the XML object of an OpenAPI document. // XMLChanges represents changes made to the XML object of an OpenAPI document.
type XMLChanges struct { type XMLChanges struct {
PropertyChanges *PropertyChanges
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
} }
// TotalChanges returns a count of everything that was changed within an XML object. // TotalChanges returns a count of everything that was changed within an XML object.
func (x *XMLChanges) TotalChanges() int { func (x *XMLChanges) TotalChanges() int {
c := x.PropertyChanges.TotalChanges() c := x.PropertyChanges.TotalChanges()
if x.ExtensionChanges != nil { if x.ExtensionChanges != nil {
c += x.ExtensionChanges.TotalChanges() c += x.ExtensionChanges.TotalChanges()
} }
return c return c
} }
// TotalBreakingChanges returns the number of breaking changes made by the XML object. // TotalBreakingChanges returns the number of breaking changes made by the XML object.
func (x *XMLChanges) TotalBreakingChanges() int { func (x *XMLChanges) TotalBreakingChanges() int {
return x.PropertyChanges.TotalBreakingChanges() return x.PropertyChanges.TotalBreakingChanges()
} }
// CompareXML will compare a left (original) and a right (new) XML instance, and check for // CompareXML will compare a left (original) and a right (new) XML instance, and check for
// any changes between them. If changes are found, the function returns a pointer to XMLChanges, // any changes between them. If changes are found, the function returns a pointer to XMLChanges,
// otherwise, if nothing changed - it will return nil // otherwise, if nothing changed - it will return nil
func CompareXML(l, r *base.XML) *XMLChanges { func CompareXML(l, r *base.XML) *XMLChanges {
xc := new(XMLChanges) xc := new(XMLChanges)
var changes []*Change var changes []*Change
var props []*PropertyCheck var props []*PropertyCheck
// Name (breaking change) // Name (breaking change)
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Name.ValueNode, LeftNode: l.Name.ValueNode,
RightNode: r.Name.ValueNode, RightNode: r.Name.ValueNode,
Label: v3.NameLabel, Label: v3.NameLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// Namespace (breaking change) // Namespace (breaking change)
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Namespace.ValueNode, LeftNode: l.Namespace.ValueNode,
RightNode: r.Namespace.ValueNode, RightNode: r.Namespace.ValueNode,
Label: v3.NamespaceLabel, Label: v3.NamespaceLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// Prefix (breaking change) // Prefix (breaking change)
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Prefix.ValueNode, LeftNode: l.Prefix.ValueNode,
RightNode: r.Prefix.ValueNode, RightNode: r.Prefix.ValueNode,
Label: v3.PrefixLabel, Label: v3.PrefixLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// Attribute (breaking change) // Attribute (breaking change)
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Attribute.ValueNode, LeftNode: l.Attribute.ValueNode,
RightNode: r.Attribute.ValueNode, RightNode: r.Attribute.ValueNode,
Label: v3.AttributeLabel, Label: v3.AttributeLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// Wrapped (breaking change) // Wrapped (breaking change)
props = append(props, &PropertyCheck{ props = append(props, &PropertyCheck{
LeftNode: l.Wrapped.ValueNode, LeftNode: l.Wrapped.ValueNode,
RightNode: r.Wrapped.ValueNode, RightNode: r.Wrapped.ValueNode,
Label: v3.WrappedLabel, Label: v3.WrappedLabel,
Changes: &changes, Changes: &changes,
Breaking: true, Breaking: true,
Original: l, Original: l,
New: r, New: r,
}) })
// check properties // check properties
CheckProperties(props) CheckProperties(props)
// check extensions // check extensions
xc.ExtensionChanges = CheckExtensions(l, r) xc.ExtensionChanges = CheckExtensions(l, r)
xc.Changes = changes xc.PropertyChanges = NewPropertyChanges(changes)
if xc.TotalChanges() <= 0 { if xc.TotalChanges() <= 0 {
return nil return nil
} }
return xc return xc
} }