Working through what changed rabbit hole

Parameter dependencies mean we're back in the ball of yarn stage. Coverage is going to drop for a bit, until all the new hashing and interfaces are in place.
This commit is contained in:
Dave Shanley
2022-10-17 06:41:29 -04:00
parent fa12f244b8
commit 9cd7e4f155
28 changed files with 1430 additions and 150 deletions

View File

@@ -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)

View File

@@ -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, "|")))
}

View File

@@ -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()
}

View File

@@ -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.
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -93,4 +93,10 @@ const (
RefLabel = "$ref"
DiscriminatorLabel = "discriminator"
ExternalDocsLabel = "externalDocs"
InLabel = "in"
AllowEmptyValueLabel = "allowEmptyValue"
StyleLabel = "style"
CollectionFormatLabel = "collectionFormat"
AllowReservedLabel = "allowReserved"
ExplodeLabel = "explode"
)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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, "|")))
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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())
}
}

9
what-changed/encoding.go Normal file
View File

@@ -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
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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 {

392
what-changed/parameter.go Normal file
View File

@@ -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
}
}

View File

@@ -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())
//
//}

View File

@@ -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]])
}
}

View File

@@ -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())
}
}

View File

@@ -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,