diff --git a/datamodel/low/base/example.go b/datamodel/low/base/example.go index b353ae3..88d7b9c 100644 --- a/datamodel/low/base/example.go +++ b/datamodel/low/base/example.go @@ -4,11 +4,14 @@ package base import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" "strconv" + "strings" ) // Example represents a low-level Example object as defined by OpenAPI 3+ @@ -26,6 +29,28 @@ func (ex *Example) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, ex.Extensions) } +// Hash will return a consistent SHA256 Hash of the Discriminator object +func (ex *Example) Hash() [32]byte { + var f []string + if ex.Summary.Value != "" { + f = append(f, ex.Summary.Value) + } + if ex.Description.Value != "" { + f = append(f, ex.Description.Value) + } + if ex.Value.Value != "" { + // this could be anything! + f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(ex.Value.Value))))) + } + if ex.ExternalValue.Value != "" { + f = append(f, ex.ExternalValue.Value) + } + for k := range ex.Extensions { + f = append(f, fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(ex.Extensions[k].Value))))) + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} + // Build extracts extensions and example value func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error { ex.Extensions = low.ExtractExtensions(root) diff --git a/datamodel/low/base/xml.go b/datamodel/low/base/xml.go index ad9cb7e..d73a75a 100644 --- a/datamodel/low/base/xml.go +++ b/datamodel/low/base/xml.go @@ -43,8 +43,12 @@ func (x *XML) Hash() [32]byte { x.Name.Value, x.Namespace.Value, x.Prefix.Value, - fmt.Sprintf("%v", x.Attribute.Value), - fmt.Sprintf("%v", x.Wrapped.Value), + fmt.Sprint(x.Attribute.Value), + fmt.Sprint(x.Wrapped.Value), + } + // add extensions to hash + for k := range x.Extensions { + d = append(d, fmt.Sprintf("%v-%x", k.Value, x.Extensions[k].Value)) } return sha256.Sum256([]byte(strings.Join(d, "|"))) } diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index 3652266..bd662fd 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -548,6 +548,9 @@ func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[ // AreEqual returns true if two Hashable objects are equal or not. func AreEqual(l, r Hashable) bool { + if l == nil || r == nil { + return false + } return l.Hash() == r.Hash() } diff --git a/datamodel/low/model_interfaces.go b/datamodel/low/model_interfaces.go new file mode 100644 index 0000000..5c734d0 --- /dev/null +++ b/datamodel/low/model_interfaces.go @@ -0,0 +1,37 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package low + +type IsParameter interface { + GetName() *NodeReference[string] + GetIn() *NodeReference[string] + GetType() *NodeReference[string] + GetDescription() *NodeReference[string] + GetRequired() *NodeReference[bool] + GetDeprecated() *NodeReference[bool] + GetAllowEmptyValue() *NodeReference[bool] + GetFormat() *NodeReference[string] + GetStyle() *NodeReference[string] + GetCollectionFormat() *NodeReference[string] + GetDefault() *NodeReference[any] + GetAllowReserved() *NodeReference[bool] + GetExplode() *NodeReference[bool] + GetMaximum() *NodeReference[int] + GetExclusiveMaximum() *NodeReference[bool] + GetMinimum() *NodeReference[int] + GetExclusiveMinimum() *NodeReference[bool] + GetMaxLength() *NodeReference[int] + GetMinLength() *NodeReference[int] + GetPattern() *NodeReference[string] + GetMaxItems() *NodeReference[int] + GetMinItems() *NodeReference[int] + GetUniqueItems() *NodeReference[bool] + GetEnum() *NodeReference[[]ValueReference[string]] + GetMultipleOf() *NodeReference[int] + GetExample() *NodeReference[any] + GetExamples() *NodeReference[any] // requires cast + GetSchema() *NodeReference[any] // requires cast. + GetContent() *NodeReference[any] // requires cast. + GetItems() *NodeReference[any] // requires cast. +} diff --git a/datamodel/low/v2/items.go b/datamodel/low/v2/items.go index aed347c..52dbab2 100644 --- a/datamodel/low/v2/items.go +++ b/datamodel/low/v2/items.go @@ -4,10 +4,13 @@ package v2 import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "strings" ) // Items is a low-level representation of a Swagger / OpenAPI 2 Items object. @@ -35,6 +38,45 @@ type Items struct { MultipleOf low.NodeReference[int] } +// Hash will return a consistent SHA256 Hash of the Items object +func (i *Items) Hash() [32]byte { + var f []string + if i.Type.Value != "" { + f = append(f, i.Type.Value) + } + if i.Format.Value != "" { + f = append(f, i.Format.Value) + } + if i.CollectionFormat.Value != "" { + f = append(f, i.CollectionFormat.Value) + } + if i.Default.Value != "" { + f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(i.Default.Value))))) + } + f = append(f, fmt.Sprint(i.Maximum.Value)) + f = append(f, fmt.Sprint(i.Minimum.Value)) + f = append(f, fmt.Sprint(i.ExclusiveMinimum.Value)) + f = append(f, fmt.Sprint(i.ExclusiveMaximum.Value)) + f = append(f, fmt.Sprint(i.MinLength.Value)) + f = append(f, fmt.Sprint(i.MaxLength.Value)) + f = append(f, fmt.Sprint(i.MinItems.Value)) + f = append(f, fmt.Sprint(i.MaxItems.Value)) + f = append(f, fmt.Sprint(i.MultipleOf.Value)) + f = append(f, fmt.Sprint(i.UniqueItems.Value)) + if i.Pattern.Value != "" { + f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(i.Pattern.Value))))) + } + if len(i.Enum.Value) > 0 { + for k := range i.Enum.Value { + f = append(f, fmt.Sprint(i.Enum.Value[k].Value)) + } + } + if i.Items.Value != nil { + f = append(f, fmt.Sprintf("%x", i.Items.Value.Hash())) + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} + // Build will build out items and default value. func (i *Items) Build(root *yaml.Node, idx *index.SpecIndex) error { items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx) diff --git a/datamodel/low/v2/parameter.go b/datamodel/low/v2/parameter.go index 8dbb9a6..5b07120 100644 --- a/datamodel/low/v2/parameter.go +++ b/datamodel/low/v2/parameter.go @@ -4,11 +4,14 @@ package v2 import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "strings" ) // Parameter represents a low-level Swagger / OpenAPI 2 Parameter object. @@ -123,3 +126,163 @@ func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { } return nil } + +// Hash will return a consistent SHA256 Hash of the Parameter object +func (p *Parameter) Hash() [32]byte { + var f []string + if p.Name.Value != "" { + f = append(f, p.Name.Value) + } + if p.In.Value != "" { + f = append(f, p.In.Value) + } + if p.Type.Value != "" { + f = append(f, p.Type.Value) + } + if p.Format.Value != "" { + f = append(f, p.Format.Value) + } + if p.Description.Value != "" { + f = append(f, p.Description.Value) + } + f = append(f, fmt.Sprint(p.Required.Value)) + f = append(f, fmt.Sprint(p.AllowEmptyValue.Value)) + if p.Schema.Value != nil { + f = append(f, fmt.Sprintf("%x", p.Schema.Value.Schema().Hash())) + } + if p.CollectionFormat.Value != "" { + f = append(f, p.CollectionFormat.Value) + } + if p.Default.Value != "" { + f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(p.Default.Value))))) + } + f = append(f, fmt.Sprint(p.Maximum.Value)) + f = append(f, fmt.Sprint(p.Minimum.Value)) + f = append(f, fmt.Sprint(p.ExclusiveMinimum.Value)) + f = append(f, fmt.Sprint(p.ExclusiveMaximum.Value)) + f = append(f, fmt.Sprint(p.MinLength.Value)) + f = append(f, fmt.Sprint(p.MaxLength.Value)) + f = append(f, fmt.Sprint(p.MinItems.Value)) + f = append(f, fmt.Sprint(p.MaxItems.Value)) + f = append(f, fmt.Sprint(p.MultipleOf.Value)) + f = append(f, fmt.Sprint(p.UniqueItems.Value)) + if p.Pattern.Value != "" { + f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(p.Pattern.Value))))) + } + if len(p.Enum.Value) > 0 { + for k := range p.Enum.Value { + f = append(f, fmt.Sprint(p.Enum.Value[k].Value)) + } + } + for k := range p.Extensions { + f = append(f, fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(p.Extensions[k].Value))))) + } + if p.Items.Value != nil { + f = append(f, fmt.Sprintf("%x", p.Items.Value.Hash())) + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} + +// IsParameter compliance methods. + +func (p *Parameter) GetName() *low.NodeReference[string] { + return &p.Name +} +func (p *Parameter) GetIn() *low.NodeReference[string] { + return &p.In +} +func (p *Parameter) GetType() *low.NodeReference[string] { + return &p.Type +} +func (p *Parameter) GetDescription() *low.NodeReference[string] { + return &p.Description +} +func (p *Parameter) GetRequired() *low.NodeReference[bool] { + return &p.Required +} +func (p *Parameter) GetDeprecated() *low.NodeReference[bool] { + // not implemented. + return nil +} +func (p *Parameter) GetAllowEmptyValue() *low.NodeReference[bool] { + return &p.AllowEmptyValue +} +func (p *Parameter) GetSchema() *low.NodeReference[any] { + i := low.NodeReference[any]{ + KeyNode: p.Schema.KeyNode, + ValueNode: p.Schema.ValueNode, + Value: p.Schema.KeyNode, + } + return &i +} +func (p *Parameter) GetFormat() *low.NodeReference[string] { + return &p.Format +} +func (p *Parameter) GetItems() *low.NodeReference[any] { + i := low.NodeReference[any]{ + KeyNode: p.Items.KeyNode, + ValueNode: p.Items.ValueNode, + Value: p.Items.KeyNode, + } + return &i +} +func (p *Parameter) GetStyle() *low.NodeReference[string] { + return nil // not implemented +} +func (p *Parameter) GetCollectionFormat() *low.NodeReference[string] { + return &p.CollectionFormat +} +func (p *Parameter) GetDefault() *low.NodeReference[any] { + return &p.Default +} +func (p *Parameter) GetAllowReserved() *low.NodeReference[bool] { + return nil // not implemented +} +func (p *Parameter) GetExplode() *low.NodeReference[bool] { + return nil // not implemented +} +func (p *Parameter) GetMaximum() *low.NodeReference[int] { + return &p.Maximum +} +func (p *Parameter) GetExclusiveMaximum() *low.NodeReference[bool] { + return &p.ExclusiveMaximum +} +func (p *Parameter) GetMinimum() *low.NodeReference[int] { + return &p.Minimum +} +func (p *Parameter) GetExclusiveMinimum() *low.NodeReference[bool] { + return &p.ExclusiveMinimum +} +func (p *Parameter) GetMaxLength() *low.NodeReference[int] { + return &p.MaxLength +} +func (p *Parameter) GetMinLength() *low.NodeReference[int] { + return &p.MinLength +} +func (p *Parameter) GetPattern() *low.NodeReference[string] { + return &p.Pattern +} +func (p *Parameter) GetMaxItems() *low.NodeReference[int] { + return &p.MaxItems +} +func (p *Parameter) GetMinItems() *low.NodeReference[int] { + return &p.MaxItems +} +func (p *Parameter) GetUniqueItems() *low.NodeReference[bool] { + return &p.UniqueItems +} +func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[string]] { + return &p.Enum +} +func (p *Parameter) GetMultipleOf() *low.NodeReference[int] { + return &p.MultipleOf +} +func (p *Parameter) GetExample() *low.NodeReference[any] { + return nil // not implemented +} +func (p *Parameter) GetExamples() *low.NodeReference[any] { + return nil // not implemented +} +func (p *Parameter) GetContent() *low.NodeReference[any] { + return nil // not implemented +} diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index 693bf96..80a2703 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -93,4 +93,10 @@ const ( RefLabel = "$ref" DiscriminatorLabel = "discriminator" ExternalDocsLabel = "externalDocs" + InLabel = "in" + AllowEmptyValueLabel = "allowEmptyValue" + StyleLabel = "style" + CollectionFormatLabel = "collectionFormat" + AllowReservedLabel = "allowReserved" + ExplodeLabel = "explode" ) diff --git a/datamodel/low/v3/encoding.go b/datamodel/low/v3/encoding.go index eb95dbc..618f7f4 100644 --- a/datamodel/low/v3/encoding.go +++ b/datamodel/low/v3/encoding.go @@ -4,9 +4,12 @@ package v3 import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" + "strings" ) // Encoding represents a low-level OpenAPI 3+ Encoding object @@ -24,6 +27,25 @@ func (en *Encoding) FindHeader(hType string) *low.ValueReference[*Header] { return low.FindItemInMap[*Header](hType, en.Headers.Value) } +// Hash will return a consistent SHA256 Hash of the Encoding object +func (en *Encoding) Hash() [32]byte { + var f []string + if en.ContentType.Value != "" { + f = append(f, en.ContentType.Value) + } + if len(en.Headers.Value) > 0 { + for k := range en.Headers.Value { + f = append(f, fmt.Sprintf("%s-%x", k.Value, en.Headers.Value[k].Value.Hash())) + } + } + if en.Style.Value != "" { + f = append(f, en.Style.Value) + } + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(en.Explode.Value))))) + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(en.AllowReserved.Value))))) + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} + // Build will extract all Header objects from supplied node. func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error { headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx) diff --git a/datamodel/low/v3/header.go b/datamodel/low/v3/header.go index 2a8702e..cde5691 100644 --- a/datamodel/low/v3/header.go +++ b/datamodel/low/v3/header.go @@ -4,11 +4,14 @@ package v3 import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "strings" ) // Header represents a low-level OpenAPI 3+ Header object. @@ -43,6 +46,42 @@ func (h *Header) FindContent(ext string) *low.ValueReference[*MediaType] { return low.FindItemInMap[*MediaType](ext, h.Content.Value) } +// Hash will return a consistent SHA256 Hash of the Header object +func (h *Header) Hash() [32]byte { + var f []string + if h.Description.Value != "" { + f = append(f, h.Description.Value) + } + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(h.Required.Value))))) + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(h.Deprecated.Value))))) + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(h.AllowEmptyValue.Value))))) + if h.Style.Value != "" { + f = append(f, h.Style.Value) + } + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(h.Explode.Value))))) + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(h.AllowReserved.Value))))) + if h.Schema.Value != nil { + f = append(f, fmt.Sprint(h.Schema.Value.Schema().Hash())) + } + if h.Example.Value != nil { + f = append(f, fmt.Sprint(sha256.Sum256([]byte(fmt.Sprint(h.Example.Value))))) + } + if len(h.Examples.Value) > 0 { + for k := range h.Examples.Value { + f = append(f, fmt.Sprintf("%s-%x", k.Value, h.Examples.Value[k].Value.Hash())) + } + } + if len(h.Content.Value) > 0 { + for k := range h.Content.Value { + f = append(f, fmt.Sprintf("%s-%x", k.Value, h.Content.Value[k].Value.Hash())) + } + } + for k := range h.Extensions { + f = append(f, fmt.Sprintf("%s-%v", k.Value, h.Extensions[k].Value)) + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} + // Build will extract extensions, examples, schema and content/media types from node. func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error { h.Extensions = low.ExtractExtensions(root) diff --git a/datamodel/low/v3/media_type.go b/datamodel/low/v3/media_type.go index 71cfd87..bac4ce4 100644 --- a/datamodel/low/v3/media_type.go +++ b/datamodel/low/v3/media_type.go @@ -4,11 +4,14 @@ package v3 import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "strings" ) // MediaType represents a low-level OpenAPI MediaType object. @@ -89,3 +92,28 @@ func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error { } return nil } + +// Hash will return a consistent SHA256 Hash of the MediaType object +func (mt *MediaType) Hash() [32]byte { + var f []string + if mt.Schema.Value != nil { + f = append(f, fmt.Sprintf("%x", mt.Schema.Value.Schema().Hash())) + } + if mt.Example.Value != nil { + f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(mt.Example.Value))))) + } + if len(mt.Examples.Value) > 0 { + for k := range mt.Examples.Value { + f = append(f, fmt.Sprintf("%s-%x", k.Value, mt.Examples.Value[k].Value.Hash())) + } + } + if len(mt.Encoding.Value) > 0 { + for k := range mt.Encoding.Value { + f = append(f, fmt.Sprintf("%s-%x", k.Value, mt.Encoding.Value[k].Value.Hash())) + } + } + for k := range mt.Extensions { + f = append(f, fmt.Sprintf("%s-%v", k.Value, mt.Extensions[k].Value)) + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} diff --git a/datamodel/low/v3/parameter.go b/datamodel/low/v3/parameter.go index 71f6758..e072e73 100644 --- a/datamodel/low/v3/parameter.go +++ b/datamodel/low/v3/parameter.go @@ -4,11 +4,14 @@ package v3 import ( + "crypto/sha256" + "fmt" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "strings" ) // Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one. @@ -91,3 +94,155 @@ func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { } return nil } + +// Hash will return a consistent SHA256 Hash of the Parameter object +func (p *Parameter) Hash() [32]byte { + var f []string + if p.Name.Value != "" { + f = append(f, p.Name.Value) + } + if p.In.Value != "" { + f = append(f, p.In.Value) + } + if p.Description.Value != "" { + f = append(f, p.Description.Value) + } + f = append(f, fmt.Sprint(p.Required.Value)) + f = append(f, fmt.Sprint(p.Deprecated.Value)) + f = append(f, fmt.Sprint(p.AllowEmptyValue.Value)) + if p.Style.Value != "" { + f = append(f, fmt.Sprint(p.Style.Value)) + } + f = append(f, fmt.Sprint(p.Explode.Value)) + f = append(f, fmt.Sprint(p.AllowReserved.Value)) + if p.Schema.Value != nil { + f = append(f, fmt.Sprintf("%x", p.Schema.Value.Schema().Hash())) + } + if p.Example.Value != nil { + f = append(f, fmt.Sprintf("%x", p.Example.Value)) + } + if len(p.Examples.Value) > 0 { + for k := range p.Examples.Value { + f = append(f, fmt.Sprintf("%s-%x", k.Value, p.Examples.Value[k].Value.Hash())) + } + } + if len(p.Extensions) > 0 { + for k := range p.Extensions { + f = append(f, fmt.Sprintf("%v-%x", k.Value, p.Extensions[k].Value)) + } + } + if len(p.Content.Value) > 0 { + for k := range p.Content.Value { + f = append(f, fmt.Sprintf("%v-%x", k.Value, p.Content.Value[k].Value.Hash())) + } + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} + +// IsParameter compliance methods. + +func (p *Parameter) GetName() *low.NodeReference[string] { + return &p.Name +} +func (p *Parameter) GetIn() *low.NodeReference[string] { + return &p.In +} +func (p *Parameter) GetType() *low.NodeReference[string] { + return nil // not implemented +} +func (p *Parameter) GetDescription() *low.NodeReference[string] { + return &p.Description +} +func (p *Parameter) GetRequired() *low.NodeReference[bool] { + return &p.Required +} +func (p *Parameter) GetDeprecated() *low.NodeReference[bool] { + return &p.Deprecated +} +func (p *Parameter) GetAllowEmptyValue() *low.NodeReference[bool] { + return &p.AllowEmptyValue +} +func (p *Parameter) GetSchema() *low.NodeReference[any] { + i := low.NodeReference[any]{ + KeyNode: p.Schema.KeyNode, + ValueNode: p.Schema.ValueNode, + Value: p.Schema.KeyNode, + } + return &i +} +func (p *Parameter) GetFormat() *low.NodeReference[string] { + return nil +} +func (p *Parameter) GetItems() *low.NodeReference[any] { + return nil +} +func (p *Parameter) GetStyle() *low.NodeReference[string] { + return &p.Style +} +func (p *Parameter) GetCollectionFormat() *low.NodeReference[string] { + return nil +} +func (p *Parameter) GetDefault() *low.NodeReference[any] { + return nil +} +func (p *Parameter) GetAllowReserved() *low.NodeReference[bool] { + return &p.AllowReserved +} +func (p *Parameter) GetExplode() *low.NodeReference[bool] { + return &p.Explode +} +func (p *Parameter) GetMaximum() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetExclusiveMaximum() *low.NodeReference[bool] { + return nil +} +func (p *Parameter) GetMinimum() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetExclusiveMinimum() *low.NodeReference[bool] { + return nil +} +func (p *Parameter) GetMaxLength() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetMinLength() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetPattern() *low.NodeReference[string] { + return nil +} +func (p *Parameter) GetMaxItems() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetMinItems() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetUniqueItems() *low.NodeReference[bool] { + return nil +} +func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[string]] { + return nil +} +func (p *Parameter) GetMultipleOf() *low.NodeReference[int] { + return nil +} +func (p *Parameter) GetExample() *low.NodeReference[any] { + return &p.Example +} +func (p *Parameter) GetExamples() *low.NodeReference[any] { + i := low.NodeReference[any]{ + KeyNode: p.Examples.KeyNode, + ValueNode: p.Examples.ValueNode, + Value: p.Examples.KeyNode, + } + return &i +} +func (p *Parameter) GetContent() *low.NodeReference[any] { + c := low.NodeReference[any]{ + KeyNode: p.Content.KeyNode, + ValueNode: p.Content.ValueNode, + Value: p.Content.Value, + } + return &c +} diff --git a/what-changed/comparison_functions.go b/what-changed/comparison_functions.go index 344d47d..5ae5a1e 100644 --- a/what-changed/comparison_functions.go +++ b/what-changed/comparison_functions.go @@ -10,12 +10,12 @@ import ( // CreateChange is a generic function that will create a Change of type T, populate all properties if set, and then // add a pointer to Change[T] in the slice of Change pointers provided -func CreateChange[T any](changes *[]*Change[T], changeType int, property string, leftValueNode, rightValueNode *yaml.Node, - breaking bool, originalObject, newObject any) *[]*Change[T] { +func CreateChange(changes *[]*Change, changeType int, property string, leftValueNode, rightValueNode *yaml.Node, + breaking bool, originalObject, newObject any) *[]*Change { // create a new context for the left and right nodes. ctx := CreateContext(leftValueNode, rightValueNode) - c := &Change[T]{ + c := &Change{ Context: ctx, ChangeType: changeType, Property: property, @@ -70,7 +70,7 @@ func FlattenLowLevelMap[T any]( } // CountBreakingChanges counts the number of changes in a slice that are breaking -func CountBreakingChanges[T any](changes []*Change[T]) int { +func CountBreakingChanges(changes []*Change) int { b := 0 for i := range changes { if changes[i].Breaking { @@ -87,17 +87,17 @@ func CountBreakingChanges[T any](changes []*Change[T]) int { // scenarios that adding things should break anything). Removals are generally breaking, except for non contract // properties like descriptions, summaries and other non-binding values, so a breakingRemove value can be tuned for // these circumstances. -func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change[T], +func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change, breakingAdd, breakingRemove bool) { var left, right T if CheckSpecificObjectRemoved(l, r, label) { left = l[label].GetValue() - CreateChange[T](changes, ObjectRemoved, label, l[label].GetValueNode(), nil, + CreateChange(changes, ObjectRemoved, label, l[label].GetValueNode(), nil, breakingRemove, left, right) } if CheckSpecificObjectAdded(l, r, label) { right = r[label].GetValue() - CreateChange[T](changes, ObjectAdded, label, nil, r[label].GetValueNode(), + CreateChange(changes, ObjectAdded, label, nil, r[label].GetValueNode(), breakingAdd, left, right) } } @@ -116,7 +116,7 @@ func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool { // for running checks on the following methods in order: // CheckPropertyAdditionOrRemoval // CheckForModification -func CheckProperties[T any](properties []*PropertyCheck[T]) { +func CheckProperties(properties []*PropertyCheck) { for _, n := range properties { CheckPropertyAdditionOrRemoval(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) CheckForModification(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) @@ -125,7 +125,7 @@ func CheckProperties[T any](properties []*PropertyCheck[T]) { // CheckPropertyAdditionOrRemoval will run both CheckForRemoval (first) and CheckForAddition (second) func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node, - label string, changes *[]*Change[T], breaking bool, orig, new T) { + label string, changes *[]*Change, breaking bool, orig, new T) { CheckForRemoval[T](l, r, label, changes, breaking, orig, new) CheckForAddition[T](l, r, label, changes, breaking, orig, new) } @@ -136,9 +136,9 @@ func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node, // PropertyRemoved // // The Change is then added to the slice of []Change[T] instances provided as a pointer. -func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { +func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change, breaking bool, orig, new T) { if l != nil && l.Value != "" && (r == nil || r.Value == "") { - CreateChange[T](changes, PropertyRemoved, label, l, r, breaking, orig, new) + CreateChange(changes, PropertyRemoved, label, l, r, breaking, orig, new) } } @@ -148,9 +148,9 @@ func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change[T] // PropertyAdded // // The Change is then added to the slice of []Change[T] instances provided as a pointer. -func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { +func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change, breaking bool, orig, new T) { if (l == nil || l.Value == "") && r != nil && r.Value != "" { - CreateChange[T](changes, PropertyAdded, label, l, r, breaking, orig, new) + CreateChange(changes, PropertyAdded, label, l, r, breaking, orig, new) } } @@ -160,13 +160,13 @@ func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change[T // If there is a change in value the function adds a change type of Modified. // // The Change is then added to the slice of []Change[T] instances provided as a pointer. -func CheckForModification[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { +func CheckForModification[T any](l, r *yaml.Node, label string, changes *[]*Change, breaking bool, orig, new T) { if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value && r.Tag == l.Tag { - CreateChange[T](changes, Modified, label, l, r, breaking, orig, new) + CreateChange(changes, Modified, label, l, r, breaking, orig, new) } // the values may have not changed, but the tag (node type) type may have if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value && r.Tag != l.Tag { - CreateChange[T](changes, Modified, label, l, r, breaking, orig, new) + CreateChange(changes, Modified, label, l, r, breaking, orig, new) } } diff --git a/what-changed/contact.go b/what-changed/contact.go index 15008f0..bb48b5d 100644 --- a/what-changed/contact.go +++ b/what-changed/contact.go @@ -10,7 +10,7 @@ import ( // ContactChanges Represent changes to a Contact object that is a child of Info, part of an OpenAPI document. type ContactChanges struct { - PropertyChanges[*base.Contact] + PropertyChanges } // TotalChanges represents the total number of changes that have occurred to a Contact object @@ -28,11 +28,11 @@ func (c *ContactChanges) TotalBreakingChanges() int { // returns nil. func CompareContact(l, r *base.Contact) *ContactChanges { - var changes []*Change[*base.Contact] - var props []*PropertyCheck[*base.Contact] + var changes []*Change + var props []*PropertyCheck // check URL - props = append(props, &PropertyCheck[*base.Contact]{ + props = append(props, &PropertyCheck{ LeftNode: l.URL.ValueNode, RightNode: r.URL.ValueNode, Label: v3.URLLabel, @@ -43,7 +43,7 @@ func CompareContact(l, r *base.Contact) *ContactChanges { }) // check name - props = append(props, &PropertyCheck[*base.Contact]{ + props = append(props, &PropertyCheck{ LeftNode: l.Name.ValueNode, RightNode: r.Name.ValueNode, Label: v3.NameLabel, @@ -54,7 +54,7 @@ func CompareContact(l, r *base.Contact) *ContactChanges { }) // check email - props = append(props, &PropertyCheck[*base.Contact]{ + props = append(props, &PropertyCheck{ LeftNode: l.Email.ValueNode, RightNode: r.Email.ValueNode, Label: v3.EmailLabel, diff --git a/what-changed/discriminator.go b/what-changed/discriminator.go index 2d2db5e..30c51e6 100644 --- a/what-changed/discriminator.go +++ b/what-changed/discriminator.go @@ -10,8 +10,8 @@ import ( // DiscriminatorChanges represents changes made to a Discriminator OpenAPI object type DiscriminatorChanges struct { - PropertyChanges[*base.Discriminator] - MappingChanges []*Change[string] + PropertyChanges + MappingChanges []*Change } // TotalChanges returns a count of everything changed within the Discriminator object @@ -35,12 +35,12 @@ func (d *DiscriminatorChanges) TotalBreakingChanges() int { // and will return a pointer to DiscriminatorChanges func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges { dc := new(DiscriminatorChanges) - var changes []*Change[*base.Discriminator] - var props []*PropertyCheck[*base.Discriminator] - var mapping []*Change[string] + var changes []*Change + var props []*PropertyCheck + var mapping []*Change // Name (breaking change) - props = append(props, &PropertyCheck[*base.Discriminator]{ + props = append(props, &PropertyCheck{ LeftNode: l.PropertyName.ValueNode, RightNode: r.PropertyName.ValueNode, Label: v3.PropertyNameLabel, @@ -63,7 +63,7 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges { // if the existing tag exists, let's check it. if rMap[i] != nil { if lMap[i].Value != rMap[i].Value { - CreateChange[string](&mapping, Modified, i, lMap[i].GetValueNode(), + CreateChange(&mapping, Modified, i, lMap[i].GetValueNode(), rMap[i].GetValueNode(), true, lMap[i].GetValue(), rMap[i].GetValue()) } } @@ -71,7 +71,7 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges { for i := range rMap { if lMap[i] == nil { - CreateChange[string](&mapping, ObjectAdded, i, nil, + CreateChange(&mapping, ObjectAdded, i, nil, rMap[i].GetValueNode(), false, nil, rMap[i].GetValue()) } } diff --git a/what-changed/encoding.go b/what-changed/encoding.go new file mode 100644 index 0000000..9622cd0 --- /dev/null +++ b/what-changed/encoding.go @@ -0,0 +1,9 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +type EncodingChanges struct { + ParameterChanges + HeaderChanges *HeaderChanges +} diff --git a/what-changed/example.go b/what-changed/example.go index 20f832c..a9bd27b 100644 --- a/what-changed/example.go +++ b/what-changed/example.go @@ -10,7 +10,7 @@ import ( // ExampleChanges represent changes to an Example object, part of an OpenAPI specification. type ExampleChanges struct { - PropertyChanges[*base.Example] + PropertyChanges ExtensionChanges *ExtensionChanges } @@ -23,16 +23,25 @@ func (e *ExampleChanges) TotalChanges() int { return l } +// TotalBreakingChanges returns the total number of breaking changes made to Example +func (e *ExampleChanges) TotalBreakingChanges() int { + l := e.PropertyChanges.TotalBreakingChanges() + if e.ExtensionChanges != nil { + l += e.ExtensionChanges.PropertyChanges.TotalBreakingChanges() + } + return l +} + // TotalChanges func CompareExamples(l, r *base.Example) *ExampleChanges { ec := new(ExampleChanges) - var changes []*Change[*base.Example] - var props []*PropertyCheck[*base.Example] + var changes []*Change + var props []*PropertyCheck // Summary - props = append(props, &PropertyCheck[*base.Example]{ + props = append(props, &PropertyCheck{ LeftNode: l.Summary.ValueNode, RightNode: r.Summary.ValueNode, Label: v3.SummaryLabel, @@ -43,7 +52,7 @@ func CompareExamples(l, r *base.Example) *ExampleChanges { }) // Description - props = append(props, &PropertyCheck[*base.Example]{ + props = append(props, &PropertyCheck{ LeftNode: l.Description.ValueNode, RightNode: r.Description.ValueNode, Label: v3.DescriptionLabel, @@ -54,7 +63,7 @@ func CompareExamples(l, r *base.Example) *ExampleChanges { }) // Value - props = append(props, &PropertyCheck[*base.Example]{ + props = append(props, &PropertyCheck{ LeftNode: l.Value.ValueNode, RightNode: r.Value.ValueNode, Label: v3.ValueLabel, @@ -65,7 +74,7 @@ func CompareExamples(l, r *base.Example) *ExampleChanges { }) // ExternalValue - props = append(props, &PropertyCheck[*base.Example]{ + props = append(props, &PropertyCheck{ LeftNode: l.ExternalValue.ValueNode, RightNode: r.ExternalValue.ValueNode, Label: v3.ExternalValue, diff --git a/what-changed/extensions.go b/what-changed/extensions.go index 89c10f3..d2b46a2 100644 --- a/what-changed/extensions.go +++ b/what-changed/extensions.go @@ -10,7 +10,7 @@ import ( // ExtensionChanges represents any changes to custom extensions defined for an OpenAPI object. type ExtensionChanges struct { - PropertyChanges[any] + PropertyChanges } func (e *ExtensionChanges) TotalChanges() int { @@ -42,15 +42,15 @@ func CompareExtensions(l, r map[low.KeyReference[string]]low.ValueReference[any] seenRight[strings.ToLower(i.Value)] = &h } - var changes []*Change[any] + var changes []*Change for i := range seenLeft { CheckForObjectAdditionOrRemoval[any](seenLeft, seenRight, i, &changes, false, true) if seenRight[i] != nil { - var props []*PropertyCheck[any] + var props []*PropertyCheck - props = append(props, &PropertyCheck[any]{ + props = append(props, &PropertyCheck{ LeftNode: seenLeft[i].ValueNode, RightNode: seenRight[i].ValueNode, Label: i, diff --git a/what-changed/external_docs.go b/what-changed/external_docs.go index 7b90c09..b2e537e 100644 --- a/what-changed/external_docs.go +++ b/what-changed/external_docs.go @@ -10,7 +10,7 @@ import ( // ExternalDocChanges represents changes made to any ExternalDoc object from an OpenAPI document. type ExternalDocChanges struct { - PropertyChanges[*base.ExternalDoc] + PropertyChanges ExtensionChanges *ExtensionChanges } @@ -32,11 +32,11 @@ func (e *ExternalDocChanges) TotalBreakingChanges() int { // 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. func CompareExternalDocs(l, r *base.ExternalDoc) *ExternalDocChanges { - var changes []*Change[*base.ExternalDoc] - var props []*PropertyCheck[*base.ExternalDoc] + var changes []*Change + var props []*PropertyCheck // URL - props = append(props, &PropertyCheck[*base.ExternalDoc]{ + props = append(props, &PropertyCheck{ LeftNode: l.URL.ValueNode, RightNode: r.URL.ValueNode, Label: v3.URLLabel, @@ -47,7 +47,7 @@ func CompareExternalDocs(l, r *base.ExternalDoc) *ExternalDocChanges { }) // description. - props = append(props, &PropertyCheck[*base.ExternalDoc]{ + props = append(props, &PropertyCheck{ LeftNode: l.Description.ValueNode, RightNode: r.Description.ValueNode, Label: v3.DescriptionLabel, diff --git a/what-changed/header_changes.go b/what-changed/header_changes.go new file mode 100644 index 0000000..94e64bc --- /dev/null +++ b/what-changed/header_changes.go @@ -0,0 +1,40 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +type HeaderChanges struct { + PropertyChanges + SchemaChanges *SchemaChanges + ExampleChanges map[string]*ExampleChanges + ContentChanges map[string]*MediaTypeChanges + ExtensionChanges *ExtensionChanges +} + +func (h *HeaderChanges) TotalChanges() int { + c := len(h.Changes) + for k := range h.ExampleChanges { + c += h.ExampleChanges[k].TotalChanges() + } + for k := range h.ContentChanges { + c += h.ContentChanges[k].TotalChanges() + } + if h.ExtensionChanges != nil { + c += h.ExtensionChanges.TotalChanges() + } + return c +} + +func (h *HeaderChanges) TotalBreakingChanges() int { + c := len(h.Changes) + for k := range h.ExampleChanges { + c += h.ExampleChanges[k].TotalChanges() + } + for k := range h.ContentChanges { + c += h.ContentChanges[k].TotalChanges() + } + if h.ExtensionChanges != nil { + c += h.ExtensionChanges.TotalChanges() + } + return c +} diff --git a/what-changed/info.go b/what-changed/info.go index c4a966c..dea44d5 100644 --- a/what-changed/info.go +++ b/what-changed/info.go @@ -10,7 +10,7 @@ import ( // InfoChanges represents the number of changes to an Info object. Part of an OpenAPI document type InfoChanges struct { - PropertyChanges[*base.Info] + PropertyChanges ContactChanges *ContactChanges LicenseChanges *LicenseChanges } @@ -37,11 +37,11 @@ func (i *InfoChanges) TotalBreakingChanges() int { // returned instead. func CompareInfo(l, r *base.Info) *InfoChanges { - var changes []*Change[*base.Info] - var props []*PropertyCheck[*base.Info] + var changes []*Change + var props []*PropertyCheck // Title - props = append(props, &PropertyCheck[*base.Info]{ + props = append(props, &PropertyCheck{ LeftNode: l.Title.ValueNode, RightNode: r.Title.ValueNode, Label: v3.TitleLabel, @@ -52,7 +52,7 @@ func CompareInfo(l, r *base.Info) *InfoChanges { }) // Description - props = append(props, &PropertyCheck[*base.Info]{ + props = append(props, &PropertyCheck{ LeftNode: l.Description.ValueNode, RightNode: r.Description.ValueNode, Label: v3.DescriptionLabel, @@ -63,7 +63,7 @@ func CompareInfo(l, r *base.Info) *InfoChanges { }) // TermsOfService - props = append(props, &PropertyCheck[*base.Info]{ + props = append(props, &PropertyCheck{ LeftNode: l.TermsOfService.ValueNode, RightNode: r.TermsOfService.ValueNode, Label: v3.TermsOfServiceLabel, @@ -74,7 +74,7 @@ func CompareInfo(l, r *base.Info) *InfoChanges { }) // Version - props = append(props, &PropertyCheck[*base.Info]{ + props = append(props, &PropertyCheck{ LeftNode: l.Version.ValueNode, RightNode: r.Version.ValueNode, Label: v3.VersionLabel, @@ -94,11 +94,11 @@ func CompareInfo(l, r *base.Info) *InfoChanges { i.ContactChanges = CompareContact(l.Contact.Value, r.Contact.Value) } else { if l.Contact.Value == nil && r.Contact.Value != nil { - CreateChange[*base.Info](&changes, ObjectAdded, v3.ContactLabel, + CreateChange(&changes, ObjectAdded, v3.ContactLabel, nil, r.Contact.ValueNode, false, nil, r.Contact.Value) } if l.Contact.Value != nil && r.Contact.Value == nil { - CreateChange[*base.Info](&changes, ObjectRemoved, v3.ContactLabel, + CreateChange(&changes, ObjectRemoved, v3.ContactLabel, l.Contact.ValueNode, nil, false, l.Contact.Value, nil) } } @@ -108,11 +108,11 @@ func CompareInfo(l, r *base.Info) *InfoChanges { i.LicenseChanges = CompareLicense(l.License.Value, r.License.Value) } else { if l.License.Value == nil && r.License.Value != nil { - CreateChange[*base.Info](&changes, ObjectAdded, v3.LicenseLabel, + CreateChange(&changes, ObjectAdded, v3.LicenseLabel, nil, r.License.ValueNode, false, nil, r.License.Value) } if l.License.Value != nil && r.License.Value == nil { - CreateChange[*base.Info](&changes, ObjectRemoved, v3.LicenseLabel, + CreateChange(&changes, ObjectRemoved, v3.LicenseLabel, l.License.ValueNode, nil, false, r.License.Value, nil) } } diff --git a/what-changed/license.go b/what-changed/license.go index ec16249..d0a9a99 100644 --- a/what-changed/license.go +++ b/what-changed/license.go @@ -10,7 +10,7 @@ import ( // LicenseChanges represent changes to a License object that is a child of Info object. Part of an OpenAPI document type LicenseChanges struct { - PropertyChanges[*base.License] + PropertyChanges } // TotalChanges represents the total number of changes made to a License instance. @@ -28,11 +28,11 @@ func (l *LicenseChanges) TotalBreakingChanges() int { // returns nil. func CompareLicense(l, r *base.License) *LicenseChanges { - var changes []*Change[*base.License] - var props []*PropertyCheck[*base.License] + var changes []*Change + var props []*PropertyCheck // check URL - props = append(props, &PropertyCheck[*base.License]{ + props = append(props, &PropertyCheck{ LeftNode: l.URL.ValueNode, RightNode: r.URL.ValueNode, Label: v3.URLLabel, @@ -43,7 +43,7 @@ func CompareLicense(l, r *base.License) *LicenseChanges { }) // check name - props = append(props, &PropertyCheck[*base.License]{ + props = append(props, &PropertyCheck{ LeftNode: l.Name.ValueNode, RightNode: r.Name.ValueNode, Label: v3.NameLabel, diff --git a/what-changed/media_type.go b/what-changed/media_type.go new file mode 100644 index 0000000..a5802fb --- /dev/null +++ b/what-changed/media_type.go @@ -0,0 +1,12 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +type MediaTypeChanges struct { + PropertyChanges + SchemaChanges *SchemaChanges + ExtensionChanges *ExtensionChanges + ExampleChanges map[string]*ExampleChanges + EncodingChanges *EncodingChanges +} diff --git a/what-changed/models.go b/what-changed/models.go index af783d8..ca5fc19 100644 --- a/what-changed/models.go +++ b/what-changed/models.go @@ -54,7 +54,7 @@ func (c *ChangeContext) HasChanged() bool { } // Change represents a change between two different elements inside an OpenAPI specification. -type Change[T any] struct { +type Change struct { // 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 @@ -83,18 +83,18 @@ type Change[T any] struct { NewObject any } -// PropertyChanges holds a slice of Change[T] change pointers -type PropertyChanges[T any] struct { - Changes []*Change[T] +// PropertyChanges holds a slice of Change pointers +type PropertyChanges struct { + Changes []*Change } // TotalChanges returns the total number of property changes made. -func (p PropertyChanges[T]) TotalChanges() int { +func (p PropertyChanges) TotalChanges() int { return len(p.Changes) } // TotalBreakingChanges returns the total number of property breaking changes made. -func (p PropertyChanges[T]) TotalBreakingChanges() int { +func (p PropertyChanges) TotalBreakingChanges() int { return CountBreakingChanges(p.Changes) } @@ -107,13 +107,13 @@ func (p PropertyChanges[T]) TotalBreakingChanges() int { //} // PropertyCheck is used by functions to check the state of left and right values. -type PropertyCheck[T any] struct { +type PropertyCheck struct { // Original is the property we're checking on the left - Original T + Original any // New is s the property we're checking on the right - New T + New any // Label is the identifier we're looking for on the left and right hand sides Label string @@ -128,7 +128,7 @@ type PropertyCheck[T any] struct { Breaking bool // Changes represents a pointer to the slice to contain all changes found. - Changes *[]*Change[T] + Changes *[]*Change } type Changes struct { diff --git a/what-changed/parameter.go b/what-changed/parameter.go new file mode 100644 index 0000000..723ffbb --- /dev/null +++ b/what-changed/parameter.go @@ -0,0 +1,392 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "fmt" + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/base" + v2 "github.com/pb33f/libopenapi/datamodel/low/v2" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "gopkg.in/yaml.v3" + "reflect" +) + +type ParameterChanges struct { + PropertyChanges + SchemaChanges *SchemaChanges + ExtensionChanges *ExtensionChanges + + // V2 change types + // ItemsChanges + + // v3 change types + ExampleChanges map[string]*ExampleChanges +} + +// TotalChanges returns a count of everything that changed +func (p *ParameterChanges) TotalChanges() int { + c := p.PropertyChanges.TotalChanges() + if p.SchemaChanges != nil { + c += p.SchemaChanges.TotalChanges() + } + if len(p.ExampleChanges) > 0 { + for i := range p.ExampleChanges { + c += p.ExampleChanges[i].TotalChanges() + } + } + if p.ExtensionChanges != nil { + c += p.ExtensionChanges.TotalChanges() + } + + return c +} + +// TotalBreakingChanges always returns 0 for ExternalDoc objects, they are non-binding. +func (p *ParameterChanges) TotalBreakingChanges() int { + c := p.PropertyChanges.TotalBreakingChanges() + if p.SchemaChanges != nil { + c += p.SchemaChanges.TotalBreakingChanges() + } + return c +} + +func addPropertyCheck(props *[]*PropertyCheck, + lvn, rvn *yaml.Node, lv, rv any, changes *[]*Change, label string, breaking bool) { + *props = append(*props, &PropertyCheck{ + LeftNode: lvn, + RightNode: rvn, + Label: label, + Changes: changes, + Breaking: breaking, + Original: lv, + New: rv, + }) +} + +func addOpenAPIParameterProperties(left, right low.IsParameter, changes *[]*Change) []*PropertyCheck { + var props []*PropertyCheck + + // style + addPropertyCheck(&props, left.GetStyle().ValueNode, right.GetStyle().ValueNode, + left.GetStyle(), right.GetStyle(), changes, v3.StyleLabel, false) + + // allow reserved + addPropertyCheck(&props, left.GetAllowReserved().ValueNode, right.GetAllowReserved().ValueNode, + left.GetAllowReserved(), right.GetAllowReserved(), changes, v3.AllowReservedLabel, true) + + // explode + addPropertyCheck(&props, left.GetExplode().ValueNode, right.GetExplode().ValueNode, + left.GetExplode(), right.GetExplode(), changes, v3.ExplodeLabel, false) + + // deprecated + addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode, + left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false) + + return props +} + +func addSwaggerParameterProperties(left, right low.IsParameter, changes *[]*Change) []*PropertyCheck { + var props []*PropertyCheck + + // type + addPropertyCheck(&props, left.GetType().ValueNode, right.GetType().ValueNode, + left.GetType(), right.GetType(), changes, v3.TypeLabel, true) + + // format + addPropertyCheck(&props, left.GetFormat().ValueNode, right.GetFormat().ValueNode, + left.GetFormat(), right.GetFormat(), changes, v3.FormatLabel, true) + + // collection format + addPropertyCheck(&props, left.GetCollectionFormat().ValueNode, right.GetCollectionFormat().ValueNode, + left.GetCollectionFormat(), right.GetCollectionFormat(), changes, v3.CollectionFormatLabel, true) + + // maximum + addPropertyCheck(&props, left.GetMaximum().ValueNode, right.GetMaximum().ValueNode, + left.GetMaximum(), right.GetMaximum(), changes, v3.MaximumLabel, true) + + // minimum + addPropertyCheck(&props, left.GetMinimum().ValueNode, right.GetMinimum().ValueNode, + left.GetMinimum(), right.GetMinimum(), changes, v3.MinimumLabel, true) + + // exclusive maximum + addPropertyCheck(&props, left.GetExclusiveMaximum().ValueNode, right.GetExclusiveMaximum().ValueNode, + left.GetExclusiveMaximum(), right.GetExclusiveMaximum(), changes, v3.ExclusiveMaximumLabel, true) + + // exclusive minimum + addPropertyCheck(&props, left.GetExclusiveMinimum().ValueNode, right.GetExclusiveMinimum().ValueNode, + left.GetExclusiveMinimum(), right.GetExclusiveMinimum(), changes, v3.ExclusiveMinimumLabel, true) + + // max length + addPropertyCheck(&props, left.GetMaxLength().ValueNode, right.GetMaxLength().ValueNode, + left.GetMaxLength(), right.GetMaxLength(), changes, v3.MaxLengthLabel, true) + + // min length + addPropertyCheck(&props, left.GetMinLength().ValueNode, right.GetMinLength().ValueNode, + left.GetMinLength(), right.GetMinLength(), changes, v3.MinLengthLabel, true) + + // pattern + addPropertyCheck(&props, left.GetPattern().ValueNode, right.GetPattern().ValueNode, + left.GetPattern(), right.GetPattern(), changes, v3.PatternLabel, true) + + // max items + addPropertyCheck(&props, left.GetMaxItems().ValueNode, right.GetMaxItems().ValueNode, + left.GetMaxItems(), right.GetMaxItems(), changes, v3.MaxItemsLabel, true) + + // min items + addPropertyCheck(&props, left.GetMinItems().ValueNode, right.GetMinItems().ValueNode, + left.GetMinItems(), right.GetMinItems(), changes, v3.MinItemsLabel, true) + + // unique items + addPropertyCheck(&props, left.GetUniqueItems().ValueNode, right.GetUniqueItems().ValueNode, + left.GetUniqueItems(), right.GetUniqueItems(), changes, v3.UniqueItemsLabel, true) + + // multiple of + addPropertyCheck(&props, left.GetMultipleOf().ValueNode, right.GetMultipleOf().ValueNode, + left.GetMultipleOf(), right.GetMultipleOf(), changes, v3.MultipleOfLabel, true) + + return props +} + +func addCommonParameterProperties(left, right low.IsParameter, changes *[]*Change) []*PropertyCheck { + var props []*PropertyCheck + + addPropertyCheck(&props, left.GetName().ValueNode, right.GetName().ValueNode, + left.GetName(), right.GetName(), changes, v3.NameLabel, true) + + // in + addPropertyCheck(&props, left.GetIn().ValueNode, right.GetIn().ValueNode, + left.GetIn(), right.GetIn(), changes, v3.InLabel, true) + + // description + addPropertyCheck(&props, left.GetDescription().ValueNode, right.GetDescription().ValueNode, + left.GetDescription(), right.GetDescription(), changes, v3.DescriptionLabel, false) + + // required + addPropertyCheck(&props, left.GetRequired().ValueNode, right.GetRequired().ValueNode, + left.GetRequired(), right.GetRequired(), changes, v3.RequiredLabel, true) + + // allow empty value + addPropertyCheck(&props, left.GetAllowEmptyValue().ValueNode, right.GetAllowEmptyValue().ValueNode, + left.GetAllowEmptyValue(), right.GetAllowEmptyValue(), changes, v3.AllowEmptyValueLabel, true) + + return props +} + +func CompareParameters(l, r any) *ParameterChanges { + + var changes []*Change + var props []*PropertyCheck + + pc := new(ParameterChanges) + var lSchema *base.SchemaProxy + var rSchema *base.SchemaProxy + 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) { + lParam := l.(*v2.Parameter) + rParam := r.(*v2.Parameter) + + // perform hash check to avoid further processing + if low.AreEqual(lParam, rParam) { + return nil + } + + props = append(props, addSwaggerParameterProperties(lParam, rParam, &changes)...) + props = append(props, addCommonParameterProperties(lParam, rParam, &changes)...) + + // extract schema + if lParam != nil { + lSchema = lParam.Schema.Value + lext = lParam.Extensions + } + if rParam != nil { + rext = rParam.Extensions + rSchema = rParam.Schema.Value + } + + // todo: items + // todo: default + // todo: enums + + } + + // OpenAPI + if reflect.TypeOf(&v3.Parameter{}) == reflect.TypeOf(l) && reflect.TypeOf(&v3.Parameter{}) == reflect.TypeOf(r) { + + lParam := l.(*v3.Parameter) + rParam := r.(*v3.Parameter) + + // perform hash check to avoid further processing + if low.AreEqual(lParam, rParam) { + return nil + } + + props = append(props, addOpenAPIParameterProperties(lParam, rParam, &changes)...) + props = append(props, addCommonParameterProperties(lParam, rParam, &changes)...) + if lParam != nil { + lext = lParam.Extensions + lSchema = lParam.Schema.Value + } + if rParam != nil { + rext = rParam.Extensions + rSchema = rParam.Schema.Value + } + + // example + if lParam.Example.Value != nil && rParam.Example.Value != nil { + if low.GenerateHashString(lParam.Example.Value) != low.GenerateHashString(rParam.Example.Value) { + CreateChange(&changes, Modified, v3.ExampleLabel, + lParam.Example.GetValueNode(), rParam.Example.GetValueNode(), false, + lParam.Example.GetValue(), rParam.Example.GetValue()) + } + } + if lParam.Example.Value == nil && rParam.Example.Value != nil { + CreateChange(&changes, PropertyAdded, v3.ExampleLabel, + nil, rParam.Example.GetValueNode(), false, + nil, rParam.Example.GetValue()) + + } + if lParam.Example.Value != nil && rParam.Example.Value == nil { + CreateChange(&changes, PropertyRemoved, v3.ExampleLabel, + lParam.Example.GetValueNode(), nil, false, + lParam.Example.GetValue(), nil) + + } + + // examples + checkParameterExamples(lParam, rParam, changes, pc) + + // todo: content + + } + CheckProperties(props) + + if lSchema != nil && rSchema != nil { + pc.SchemaChanges = CompareSchemas(lSchema, rSchema) + } + if lSchema != nil && rSchema == nil { + CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, + lSchema.GetValueNode(), nil, true, lSchema, + nil) + } + + if lSchema == nil && rSchema != nil { + CreateChange(&changes, ObjectAdded, v3.SchemaLabel, + nil, rSchema.GetValueNode(), true, nil, + rSchema) + } + + pc.Changes = changes + pc.ExtensionChanges = CompareExtensions(lext, rext) + + if pc.TotalChanges() > 0 { + return pc + } + return nil +} + +func checkParameterExamples(lParam *v3.Parameter, rParam *v3.Parameter, changes []*Change, pc *ParameterChanges) { + lExpHashes := make(map[string]string) + rExpHashes := make(map[string]string) + lExpValues := make(map[string]low.ValueReference[*base.Example]) + rExpValues := make(map[string]low.ValueReference[*base.Example]) + if lParam != nil && lParam.Examples.Value != nil { + for k := range lParam.Examples.Value { + lExpHashes[k.Value] = fmt.Sprintf("%x", lParam.Examples.Value[k].Value.Hash()) + lExpValues[k.Value] = lParam.Examples.Value[k] + } + } + if rParam != nil && rParam.Examples.Value != nil { + for k := range rParam.Examples.Value { + rExpHashes[k.Value] = fmt.Sprintf("%x", rParam.Examples.Value[k].Value.Hash()) + rExpValues[k.Value] = rParam.Examples.Value[k] + } + } + expChanges := make(map[string]*ExampleChanges) + + // check left example hashes + for k := range lExpHashes { + rhash := rExpHashes[k] + if rhash == "" { + CreateChange(&changes, ObjectRemoved, v3.ExamplesLabel, + lExpValues[k].GetValueNode(), nil, false, + lExpValues[k].GetValue(), nil) + continue + } + if lExpHashes[k] == rExpHashes[k] { + continue + } + expChanges[k] = CompareExamples(lExpValues[k].Value, rExpValues[k].Value) + } + + //check right example hashes + for k := range rExpHashes { + lhash := lExpHashes[k] + if lhash == "" { + CreateChange(&changes, ObjectAdded, v3.ExamplesLabel, + nil, lExpValues[k].GetValueNode(), false, + nil, lExpValues[k].GetValue()) + continue + } + } + + if len(expChanges) > 0 { + pc.ExampleChanges = expChanges + } +} + +func checkParameterContent(lParam *v3.Parameter, rParam *v3.Parameter, changes []*Change, pc *ParameterChanges) { + lConHashes := make(map[string]string) + rConHashes := make(map[string]string) + lConValues := make(map[string]low.ValueReference[*v3.MediaType]) + rConValues := make(map[string]low.ValueReference[*v3.MediaType]) + if lParam != nil && lParam.Content.Value != nil { + for k := range lParam.Content.Value { + lConHashes[k.Value] = fmt.Sprintf("%x", lParam.Content.Value[k].Value.Hash()) + lConValues[k.Value] = lParam.Content.Value[k] + } + } + if rParam != nil && rParam.Content.Value != nil { + for k := range rParam.Content.Value { + rConHashes[k.Value] = fmt.Sprintf("%x", rParam.Content.Value[k].Value.Hash()) + rConValues[k.Value] = rParam.Content.Value[k] + } + } + expChanges := make(map[string]*ExampleChanges) + + // check left example hashes + for k := range lConHashes { + rhash := rConHashes[k] + if rhash == "" { + CreateChange(&changes, ObjectRemoved, v3.ExamplesLabel, + lConValues[k].GetValueNode(), nil, false, + lConValues[k].GetValue(), nil) + continue + } + if lConHashes[k] == rConHashes[k] { + continue + } + + // Compare media types. + //expChanges[k] = CompareM(lConValues[k].Value, rConValues[k].Value) + // todo: start here <-------- + + } + + //check right example hashes + for k := range rConHashes { + lhash := lConHashes[k] + if lhash == "" { + CreateChange(&changes, ObjectAdded, v3.ExamplesLabel, + nil, lConValues[k].GetValueNode(), false, + nil, lConValues[k].GetValue()) + continue + } + } + + if len(expChanges) > 0 { + pc.ExampleChanges = expChanges + } +} diff --git a/what-changed/parameter_test.go b/what-changed/parameter_test.go new file mode 100644 index 0000000..aa4a5bc --- /dev/null +++ b/what-changed/parameter_test.go @@ -0,0 +1,286 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCompareParameters_V3(t *testing.T) { + + left := `name: a param` + right := `name: a parama` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) +} + +func TestCompareParameters_V3_Schema(t *testing.T) { + + left := `schema: + description: something new` + right := `schema: + description: a changed thing` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, 1, extChanges.SchemaChanges.TotalChanges()) + +} + +func TestCompareParameters_V3_SchemaAdd(t *testing.T) { + + left := `description: hello` + right := `description: hello +schema: + description: a changed thing` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) + +} + +func TestCompareParameters_V3_SchemaRemove(t *testing.T) { + + left := `description: hello` + right := `description: hello +schema: + description: a changed thing` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) + +} + +func TestCompareParameters_V3_Extensions(t *testing.T) { + + left := `x-thing: thang` + right := `x-thing: dang` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, 1, extChanges.ExtensionChanges.TotalChanges()) + +} + +//func TestCompareParameters_V3_ExampleChange(t *testing.T) { +// +// left := `example: a string` +// right := `example: +// now: an object` +// +// var lNode, rNode yaml.Node +// _ = yaml.Unmarshal([]byte(left), &lNode) +// _ = yaml.Unmarshal([]byte(right), &rNode) +// +// // create low level objects +// var lDoc v3.Parameter +// var rDoc v3.Parameter +// _ = low.BuildModel(&lNode, &lDoc) +// _ = low.BuildModel(&rNode, &rDoc) +// _ = lDoc.Build(lNode.Content[0], nil) +// _ = rDoc.Build(rNode.Content[0], nil) +// +// // compare. +// extChanges := CompareParameters(&lDoc, &rDoc) +// assert.Equal(t, 1, extChanges.TotalChanges()) +// assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +// assert.Equal(t, 1, extChanges.ExtensionChanges.TotalChanges()) +//} + +func TestCompareParameters_V3_ExampleEqual(t *testing.T) { + + left := `example: a string` + right := `example: a string` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareParameters_V3_ExampleAdd(t *testing.T) { + + left := `description: something` + right := `description: something +example: a string` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) +} + +func TestCompareParameters_V3_ExampleRemove(t *testing.T) { + + left := `description: something` + right := `description: something +example: a string` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Parameter + var rDoc v3.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareParameters(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) +} + +func TestCompareParameters_V2(t *testing.T) { + + left := `name: a param` + right := `name: a parama` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.Parameter + var rDoc v2.Parameter + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareParameters(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) +} + +//func TestCompareParameters_V2_Extensions(t *testing.T) { +// +// left := `x-thing: thang` +// right := `x-thing: dang` +// +// var lNode, rNode yaml.Node +// _ = yaml.Unmarshal([]byte(left), &lNode) +// _ = yaml.Unmarshal([]byte(right), &rNode) +// +// // create low level objects +// var lDoc v2.Parameter +// var rDoc v2.Parameter +// _ = low.BuildModel(&lNode, &lDoc) +// _ = low.BuildModel(&rNode, &rDoc) +// _ = lDoc.Build(lNode.Content[0], nil) +// _ = rDoc.Build(rNode.Content[0], nil) +// +// // compare. +// extChanges := CompareParameters(&lDoc, &rDoc) +// assert.Equal(t, 1, extChanges.TotalChanges()) +// assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +// assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) +// assert.Equal(t, 1, extChanges.ExtensionChanges.TotalChanges()) +// +//} diff --git a/what-changed/schema.go b/what-changed/schema.go index a1512aa..898c57a 100644 --- a/what-changed/schema.go +++ b/what-changed/schema.go @@ -13,8 +13,14 @@ import ( "sync" ) +// SchemaChanges represent all changes to a base.Schema OpenAPI object. These changes are represented +// by all versions of OpenAPI. +// +// Any additions or removals to slice based results will be recorded in the PropertyChanges of the parent +// changes, and not the child for example, adding a new schema to `anyOf` will create a new change result in +// PropertyChanges.Changes, and not in the AnyOfChanges property. type SchemaChanges struct { - PropertyChanges[*base.Schema] + PropertyChanges DiscriminatorChanges *DiscriminatorChanges AllOfChanges []*SchemaChanges AnyOfChanges []*SchemaChanges @@ -27,6 +33,7 @@ type SchemaChanges struct { ExtensionChanges *ExtensionChanges } +// TotalChanges returns a count of the total number of changes made to this schema and all sub-schemas func (s *SchemaChanges) TotalChanges() int { t := s.PropertyChanges.TotalChanges() if s.DiscriminatorChanges != nil { @@ -74,6 +81,7 @@ func (s *SchemaChanges) TotalChanges() int { return t } +// TotalBreakingChanges returns the total number of breaking changes made to this schema and all sub-schemas. func (s *SchemaChanges) TotalBreakingChanges() int { t := s.PropertyChanges.TotalBreakingChanges() if s.DiscriminatorChanges != nil { @@ -122,18 +130,18 @@ func (s *SchemaChanges) TotalBreakingChanges() int { func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { sc := new(SchemaChanges) - var changes []*Change[*base.Schema] + var changes []*Change // Added if l == nil && r != nil { - CreateChange[*base.Schema](&changes, ObjectAdded, v3.SchemaLabel, + CreateChange(&changes, ObjectAdded, v3.SchemaLabel, nil, nil, true, nil, r) sc.Changes = changes } // Removed if l != nil && r == nil { - CreateChange[*base.Schema](&changes, ObjectRemoved, v3.SchemaLabel, + CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, nil, nil, true, l, nil) sc.Changes = changes } @@ -148,7 +156,7 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { return nil } else { // references are different, that's all we care to know. - CreateChange[*base.Schema](&changes, Modified, v3.RefLabel, + CreateChange(&changes, Modified, v3.RefLabel, l.GetValueNode().Content[1], r.GetValueNode().Content[1], true, l.GetSchemaReference(), r.GetSchemaReference()) sc.Changes = changes @@ -158,7 +166,7 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { // changed from inline to ref if !l.IsSchemaReference() && r.IsSchemaReference() { - CreateChange[*base.Schema](&changes, Modified, v3.RefLabel, + CreateChange(&changes, Modified, v3.RefLabel, l.GetValueNode(), r.GetValueNode().Content[1], true, l, r.GetSchemaReference()) sc.Changes = changes return sc // we're done here @@ -166,7 +174,7 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { // changed from ref to inline if l.IsSchemaReference() && !r.IsSchemaReference() { - CreateChange[*base.Schema](&changes, Modified, v3.RefLabel, + CreateChange(&changes, Modified, v3.RefLabel, l.GetValueNode().Content[1], r.GetValueNode(), true, l.GetSchemaReference(), r) sc.Changes = changes return sc // done, nothing else to do. @@ -181,7 +189,7 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { } // check XML - checkXML(lSchema, rSchema, &changes, sc) + checkSchemaXML(lSchema, rSchema, &changes, sc) // check examples checkExamples(lSchema, rSchema, &changes) @@ -226,15 +234,15 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { return sc } -func checkXML(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Change[*base.Schema], sc *SchemaChanges) { +func checkSchemaXML(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Change, sc *SchemaChanges) { // XML removed if lSchema.XML.Value != nil && rSchema.XML.Value == nil { - CreateChange[*base.Schema](changes, ObjectRemoved, v3.XMLLabel, + CreateChange(changes, ObjectRemoved, v3.XMLLabel, lSchema.XML.GetValueNode(), nil, true, lSchema.XML.GetValue(), nil) } // XML added if lSchema.XML.Value == nil && rSchema.XML.Value != nil { - CreateChange[*base.Schema](changes, ObjectAdded, v3.XMLLabel, + CreateChange(changes, ObjectAdded, v3.XMLLabel, nil, rSchema.XML.GetValueNode(), false, nil, rSchema.XML.GetValue()) } @@ -249,7 +257,7 @@ func checkXML(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Change[*ba func checkPropertiesPropertyOfASchema( lSchema *base.Schema, rSchema *base.Schema, - changes *[]*Change[*base.Schema], + changes *[]*Change, sc *SchemaChanges, doneChan chan bool) int { @@ -310,10 +318,10 @@ func checkPropertiesPropertyOfASchema( if lProps[w] != rProps[w] { // old removed, new added. - CreateChange[*base.Schema](changes, ObjectAdded, v3.PropertiesLabel, + CreateChange(changes, ObjectAdded, v3.PropertiesLabel, nil, rKeyNodes[rProps[w]], false, nil, rEntities[rProps[w]]) - CreateChange[*base.Schema](changes, ObjectRemoved, v3.PropertiesLabel, + CreateChange(changes, ObjectRemoved, v3.PropertiesLabel, lKeyNodes[lProps[w]], nil, true, lEntities[lProps[w]], nil) } @@ -328,7 +336,7 @@ func checkPropertiesPropertyOfASchema( go checkProperty(lProps[w], lEntities[lProps[w]], rEntities[rProps[w]], propChanges, doneChan) } if w >= len(rProps) { - CreateChange[*base.Schema](changes, ObjectRemoved, v3.PropertiesLabel, + CreateChange(changes, ObjectRemoved, v3.PropertiesLabel, lKeyNodes[lProps[w]], nil, true, lEntities[lProps[w]], nil) } } @@ -342,7 +350,7 @@ func checkPropertiesPropertyOfASchema( go checkProperty(rProps[w], lEntities[lProps[w]], rEntities[rProps[w]], propChanges, doneChan) } if w >= len(lProps) { - CreateChange[*base.Schema](changes, ObjectAdded, v3.PropertiesLabel, + CreateChange(changes, ObjectAdded, v3.PropertiesLabel, nil, rKeyNodes[rProps[w]], false, nil, rEntities[rProps[w]]) } } @@ -354,12 +362,12 @@ func checkPropertiesPropertyOfASchema( func checkSchemaPropertyChanges( lSchema *base.Schema, rSchema *base.Schema, - changes *[]*Change[*base.Schema], sc *SchemaChanges) { + changes *[]*Change, sc *SchemaChanges) { - var props []*PropertyCheck[*base.Schema] + var props []*PropertyCheck // $schema (breaking change) - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.SchemaTypeRef.ValueNode, RightNode: rSchema.SchemaTypeRef.ValueNode, Label: v3.SchemaDialectLabel, @@ -370,7 +378,7 @@ func checkSchemaPropertyChanges( }) // ExclusiveMaximum - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.ExclusiveMaximum.ValueNode, RightNode: rSchema.ExclusiveMaximum.ValueNode, Label: v3.ExclusiveMaximumLabel, @@ -381,7 +389,7 @@ func checkSchemaPropertyChanges( }) // ExclusiveMinimum - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.ExclusiveMinimum.ValueNode, RightNode: rSchema.ExclusiveMinimum.ValueNode, Label: v3.ExclusiveMinimumLabel, @@ -392,7 +400,7 @@ func checkSchemaPropertyChanges( }) // Type - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Type.ValueNode, RightNode: rSchema.Type.ValueNode, Label: v3.TypeLabel, @@ -403,7 +411,7 @@ func checkSchemaPropertyChanges( }) // Title - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Title.ValueNode, RightNode: rSchema.Title.ValueNode, Label: v3.TitleLabel, @@ -414,7 +422,7 @@ func checkSchemaPropertyChanges( }) // MultipleOf - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MultipleOf.ValueNode, RightNode: rSchema.MultipleOf.ValueNode, Label: v3.MultipleOfLabel, @@ -425,7 +433,7 @@ func checkSchemaPropertyChanges( }) // Maximum - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Maximum.ValueNode, RightNode: rSchema.Maximum.ValueNode, Label: v3.MaximumLabel, @@ -436,7 +444,7 @@ func checkSchemaPropertyChanges( }) // Minimum - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Minimum.ValueNode, RightNode: rSchema.Minimum.ValueNode, Label: v3.MinimumLabel, @@ -447,7 +455,7 @@ func checkSchemaPropertyChanges( }) // MaxLength - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MaxLength.ValueNode, RightNode: rSchema.MaxLength.ValueNode, Label: v3.MaxLengthLabel, @@ -458,7 +466,7 @@ func checkSchemaPropertyChanges( }) // MinLength - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MinLength.ValueNode, RightNode: rSchema.MinLength.ValueNode, Label: v3.MinLengthLabel, @@ -469,7 +477,7 @@ func checkSchemaPropertyChanges( }) // Pattern - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Pattern.ValueNode, RightNode: rSchema.Pattern.ValueNode, Label: v3.PatternLabel, @@ -480,7 +488,7 @@ func checkSchemaPropertyChanges( }) // Format - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Format.ValueNode, RightNode: rSchema.Format.ValueNode, Label: v3.FormatLabel, @@ -491,7 +499,7 @@ func checkSchemaPropertyChanges( }) // MaxItems - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MaxItems.ValueNode, RightNode: rSchema.MaxItems.ValueNode, Label: v3.MaxItemsLabel, @@ -502,7 +510,7 @@ func checkSchemaPropertyChanges( }) // MinItems - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MinItems.ValueNode, RightNode: rSchema.MinItems.ValueNode, Label: v3.MinItemsLabel, @@ -513,7 +521,7 @@ func checkSchemaPropertyChanges( }) // MaxProperties - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MaxProperties.ValueNode, RightNode: rSchema.MaxProperties.ValueNode, Label: v3.MaxPropertiesLabel, @@ -524,7 +532,7 @@ func checkSchemaPropertyChanges( }) // MinProperties - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.MinProperties.ValueNode, RightNode: rSchema.MinProperties.ValueNode, Label: v3.MinPropertiesLabel, @@ -535,7 +543,7 @@ func checkSchemaPropertyChanges( }) // UniqueItems - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.UniqueItems.ValueNode, RightNode: rSchema.UniqueItems.ValueNode, Label: v3.UniqueItemsLabel, @@ -546,7 +554,7 @@ func checkSchemaPropertyChanges( }) // AdditionalProperties - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.AdditionalProperties.ValueNode, RightNode: rSchema.AdditionalProperties.ValueNode, Label: v3.AdditionalPropertiesLabel, @@ -557,7 +565,7 @@ func checkSchemaPropertyChanges( }) // Description - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Description.ValueNode, RightNode: rSchema.Description.ValueNode, Label: v3.DescriptionLabel, @@ -568,7 +576,7 @@ func checkSchemaPropertyChanges( }) // ContentEncoding - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.ContentEncoding.ValueNode, RightNode: rSchema.ContentEncoding.ValueNode, Label: v3.ContentEncodingLabel, @@ -579,7 +587,7 @@ func checkSchemaPropertyChanges( }) // ContentMediaType - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.ContentMediaType.ValueNode, RightNode: rSchema.ContentMediaType.ValueNode, Label: v3.ContentMediaType, @@ -590,7 +598,7 @@ func checkSchemaPropertyChanges( }) // Default - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Default.ValueNode, RightNode: rSchema.Default.ValueNode, Label: v3.DefaultLabel, @@ -601,7 +609,7 @@ func checkSchemaPropertyChanges( }) // Nullable - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Nullable.ValueNode, RightNode: rSchema.Nullable.ValueNode, Label: v3.NullableLabel, @@ -612,7 +620,7 @@ func checkSchemaPropertyChanges( }) // ReadOnly - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.ReadOnly.ValueNode, RightNode: rSchema.ReadOnly.ValueNode, Label: v3.ReadOnlyLabel, @@ -623,7 +631,7 @@ func checkSchemaPropertyChanges( }) // WriteOnly - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.WriteOnly.ValueNode, RightNode: rSchema.WriteOnly.ValueNode, Label: v3.WriteOnlyLabel, @@ -634,7 +642,7 @@ func checkSchemaPropertyChanges( }) // Example - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Example.ValueNode, RightNode: rSchema.Example.ValueNode, Label: v3.ExampleLabel, @@ -645,7 +653,7 @@ func checkSchemaPropertyChanges( }) // Deprecated - props = append(props, &PropertyCheck[*base.Schema]{ + props = append(props, &PropertyCheck{ LeftNode: lSchema.Deprecated.ValueNode, RightNode: rSchema.Deprecated.ValueNode, Label: v3.DeprecatedLabel, @@ -666,14 +674,14 @@ func checkSchemaPropertyChanges( } for g := range k { if _, ok := j[g]; !ok { - CreateChange[*base.Schema](changes, PropertyAdded, v3.RequiredLabel, + CreateChange(changes, PropertyAdded, v3.RequiredLabel, nil, rSchema.Required.Value[k[g]].GetValueNode(), true, nil, rSchema.Required.Value[k[g]].GetValue) } } for g := range j { if _, ok := k[g]; !ok { - CreateChange[*base.Schema](changes, PropertyRemoved, v3.RequiredLabel, + CreateChange(changes, PropertyRemoved, v3.RequiredLabel, lSchema.Required.Value[j[g]].GetValueNode(), nil, true, lSchema.Required.Value[j[g]].GetValue, nil) } @@ -690,14 +698,14 @@ func checkSchemaPropertyChanges( } for g := range k { if _, ok := j[g]; !ok { - CreateChange[*base.Schema](changes, PropertyAdded, v3.EnumLabel, + CreateChange(changes, PropertyAdded, v3.EnumLabel, nil, rSchema.Enum.Value[k[g]].GetValueNode(), false, nil, rSchema.Enum.Value[k[g]].GetValue) } } for g := range j { if _, ok := k[g]; !ok { - CreateChange[*base.Schema](changes, PropertyRemoved, v3.EnumLabel, + CreateChange(changes, PropertyRemoved, v3.EnumLabel, lSchema.Enum.Value[j[g]].GetValueNode(), nil, true, lSchema.Enum.Value[j[g]].GetValue, nil) } @@ -712,12 +720,12 @@ func checkSchemaPropertyChanges( } // added Discriminator if lSchema.Discriminator.Value == nil && rSchema.Discriminator.Value != nil { - CreateChange[*base.Schema](changes, ObjectAdded, v3.DiscriminatorLabel, + CreateChange(changes, ObjectAdded, v3.DiscriminatorLabel, nil, rSchema.Discriminator.ValueNode, true, nil, rSchema.Discriminator.Value) } // removed Discriminator if lSchema.Discriminator.Value != nil && rSchema.Discriminator.Value == nil { - CreateChange[*base.Schema](changes, ObjectRemoved, v3.DiscriminatorLabel, + CreateChange(changes, ObjectRemoved, v3.DiscriminatorLabel, lSchema.Discriminator.ValueNode, nil, true, lSchema.Discriminator.Value, nil) } @@ -730,12 +738,12 @@ func checkSchemaPropertyChanges( } // added ExternalDocs if lSchema.ExternalDocs.Value == nil && rSchema.ExternalDocs.Value != nil { - CreateChange[*base.Schema](changes, ObjectAdded, v3.ExternalDocsLabel, + CreateChange(changes, ObjectAdded, v3.ExternalDocsLabel, nil, rSchema.ExternalDocs.ValueNode, false, nil, rSchema.ExternalDocs.Value) } // removed ExternalDocs if lSchema.ExternalDocs.Value != nil && rSchema.ExternalDocs.Value == nil { - CreateChange[*base.Schema](changes, ObjectRemoved, v3.ExternalDocsLabel, + CreateChange(changes, ObjectRemoved, v3.ExternalDocsLabel, lSchema.ExternalDocs.ValueNode, nil, false, lSchema.ExternalDocs.Value, nil) } @@ -746,7 +754,7 @@ func checkSchemaPropertyChanges( CheckProperties(props) } -func checkExamples(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Change[*base.Schema]) { +func checkExamples(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Change) { // check examples (3.1+) var lExampKey, rExampKey []string lExampN := make(map[string]*yaml.Node) @@ -777,7 +785,7 @@ func checkExamples(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Chang if len(lExampKey) == len(rExampKey) { for i := range lExampKey { if lExampKey[i] != rExampKey[i] { - CreateChange[*base.Schema](changes, Modified, v3.ExamplesLabel, + CreateChange(changes, Modified, v3.ExamplesLabel, lExampN[lExampKey[i]], rExampN[rExampKey[i]], false, lExampVal[lExampKey[i]], rExampVal[rExampKey[i]]) } @@ -787,12 +795,12 @@ func checkExamples(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Chang if len(lExampKey) > len(rExampKey) { for i := range lExampKey { if i < len(rExampKey) && lExampKey[i] != rExampKey[i] { - CreateChange[*base.Schema](changes, Modified, v3.ExamplesLabel, + CreateChange(changes, Modified, v3.ExamplesLabel, lExampN[lExampKey[i]], rExampN[rExampKey[i]], false, lExampVal[lExampKey[i]], rExampVal[rExampKey[i]]) } if i >= len(rExampKey) { - CreateChange[*base.Schema](changes, ObjectRemoved, v3.ExamplesLabel, + CreateChange(changes, ObjectRemoved, v3.ExamplesLabel, lExampN[lExampKey[i]], nil, false, lExampVal[lExampKey[i]], nil) } @@ -803,12 +811,12 @@ func checkExamples(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Chang if len(lExampKey) < len(rExampKey) { for i := range rExampKey { if i < len(lExampKey) && lExampKey[i] != rExampKey[i] { - CreateChange[*base.Schema](changes, Modified, v3.ExamplesLabel, + CreateChange(changes, Modified, v3.ExamplesLabel, lExampN[lExampKey[i]], rExampN[rExampKey[i]], false, lExampVal[lExampKey[i]], rExampVal[rExampKey[i]]) } if i >= len(lExampKey) { - CreateChange[*base.Schema](changes, ObjectAdded, v3.ExamplesLabel, + CreateChange(changes, ObjectAdded, v3.ExamplesLabel, nil, rExampN[rExampKey[i]], false, nil, rExampVal[rExampKey[i]]) } @@ -821,7 +829,7 @@ func extractSchemaChanges( rSchema []low.ValueReference[*base.SchemaProxy], label string, sc *[]*SchemaChanges, - changes *[]*Change[*base.Schema], + changes *[]*Change, done chan bool) { // if there is nothing here, there is nothing to do. @@ -876,7 +884,7 @@ func extractSchemaChanges( *sc = append(*sc, CompareSchemas(lEntities[lKeys[w]], rEntities[rKeys[w]])) } if w >= len(rKeys) { - CreateChange[*base.Schema](changes, ObjectRemoved, label, + CreateChange(changes, ObjectRemoved, label, lEntities[lKeys[w]].GetValueNode(), nil, true, lEntities[lKeys[w]], nil) } } @@ -889,7 +897,7 @@ func extractSchemaChanges( *sc = append(*sc, CompareSchemas(lEntities[lKeys[w]], rEntities[rKeys[w]])) } if w >= len(lKeys) { - CreateChange[*base.Schema](changes, ObjectAdded, label, + CreateChange(changes, ObjectAdded, label, nil, rEntities[rKeys[w]].GetValueNode(), false, nil, rEntities[rKeys[w]]) } } diff --git a/what-changed/tags.go b/what-changed/tags.go index 3644057..6f9f0f4 100644 --- a/what-changed/tags.go +++ b/what-changed/tags.go @@ -12,7 +12,7 @@ import ( // TagChanges represents changes made to the Tags object of an OpenAPI document. type TagChanges struct { - PropertyChanges[*base.Tag] + PropertyChanges ExternalDocs *ExternalDocChanges ExtensionChanges *ExtensionChanges } @@ -52,7 +52,7 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges { seenRight[strings.ToLower(r[i].Value.Name.Value)] = &h } - var changes []*Change[*base.Tag] + var changes []*Change // check for removals, modifications and moves for i := range seenLeft { @@ -62,10 +62,10 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges { // if the existing tag exists, let's check it. if seenRight[i] != nil { - var props []*PropertyCheck[*base.Tag] + var props []*PropertyCheck // Name - props = append(props, &PropertyCheck[*base.Tag]{ + props = append(props, &PropertyCheck{ LeftNode: seenLeft[i].Value.Name.ValueNode, RightNode: seenRight[i].Value.Name.ValueNode, Label: v3.NameLabel, @@ -76,7 +76,7 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges { }) // Description - props = append(props, &PropertyCheck[*base.Tag]{ + props = append(props, &PropertyCheck{ LeftNode: seenLeft[i].Value.Description.ValueNode, RightNode: seenRight[i].Value.Description.ValueNode, Label: v3.DescriptionLabel, @@ -101,7 +101,7 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges { for i := range seenRight { if seenLeft[i] == nil { - CreateChange[*base.Tag](&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(), + CreateChange(&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(), false, nil, seenRight[i].GetValue()) } } diff --git a/what-changed/xml.go b/what-changed/xml.go index 2fb0bc1..7270f09 100644 --- a/what-changed/xml.go +++ b/what-changed/xml.go @@ -10,7 +10,7 @@ import ( // XMLChanges represents changes made to the XML object of an OpenAPI document. type XMLChanges struct { - PropertyChanges[*base.XML] + PropertyChanges ExtensionChanges *ExtensionChanges } @@ -33,11 +33,11 @@ func (x *XMLChanges) TotalBreakingChanges() int { // otherwise, if nothing changed - it will return nil func CompareXML(l, r *base.XML) *XMLChanges { xc := new(XMLChanges) - var changes []*Change[*base.XML] - var props []*PropertyCheck[*base.XML] + var changes []*Change + var props []*PropertyCheck // Name (breaking change) - props = append(props, &PropertyCheck[*base.XML]{ + props = append(props, &PropertyCheck{ LeftNode: l.Name.ValueNode, RightNode: r.Name.ValueNode, Label: v3.NameLabel, @@ -48,7 +48,7 @@ func CompareXML(l, r *base.XML) *XMLChanges { }) // Namespace (breaking change) - props = append(props, &PropertyCheck[*base.XML]{ + props = append(props, &PropertyCheck{ LeftNode: l.Namespace.ValueNode, RightNode: r.Namespace.ValueNode, Label: v3.NamespaceLabel, @@ -59,7 +59,7 @@ func CompareXML(l, r *base.XML) *XMLChanges { }) // Prefix (breaking change) - props = append(props, &PropertyCheck[*base.XML]{ + props = append(props, &PropertyCheck{ LeftNode: l.Prefix.ValueNode, RightNode: r.Prefix.ValueNode, Label: v3.PrefixLabel, @@ -70,7 +70,7 @@ func CompareXML(l, r *base.XML) *XMLChanges { }) // Attribute (breaking change) - props = append(props, &PropertyCheck[*base.XML]{ + props = append(props, &PropertyCheck{ LeftNode: l.Attribute.ValueNode, RightNode: r.Attribute.ValueNode, Label: v3.AttributeLabel, @@ -81,7 +81,7 @@ func CompareXML(l, r *base.XML) *XMLChanges { }) // Wrapped (breaking change) - props = append(props, &PropertyCheck[*base.XML]{ + props = append(props, &PropertyCheck{ LeftNode: l.Wrapped.ValueNode, RightNode: r.Wrapped.ValueNode, Label: v3.WrappedLabel,