mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-07 20:47:45 +00:00
Wired up multi-version handling patterns
Designs for handling multiple versions of objects have been set, seems clean and scalable. Generic functions for handling maps has been added also, which will cut down time moving forward.
This commit is contained in:
@@ -104,7 +104,7 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found",
|
||||
root.Value, root.Line, root.Column)
|
||||
rv, root.Line, root.Column)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecInd
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("object extraciton failed: %s", err.Error())
|
||||
return nil, fmt.Errorf("object extraction failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
return NodeReference[T]{}, fmt.Errorf("object extraciton failed: %s", err.Error())
|
||||
return NodeReference[T]{}, fmt.Errorf("object extraction failed: %s", err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -172,7 +172,7 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
|
||||
}
|
||||
} else {
|
||||
if lerr != nil {
|
||||
return NodeReference[T]{}, fmt.Errorf("object extraciton failed: %s", lerr.Error())
|
||||
return NodeReference[T]{}, fmt.Errorf("object extraction failed: %s", lerr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,7 +554,13 @@ func AreEqual(l, r Hashable) bool {
|
||||
return l.Hash() == r.Hash()
|
||||
}
|
||||
|
||||
// GenerateHashString will generate a SHA36 hash of any object passed in.
|
||||
// GenerateHashString will generate a SHA36 hash of any object passed in. If the object is Hashable
|
||||
// then the underlying Hash() method will be called.
|
||||
func GenerateHashString(v any) string {
|
||||
if h, ok := v.(Hashable); ok {
|
||||
if h != nil {
|
||||
return fmt.Sprintf("%x", h.Hash())
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(v))))
|
||||
}
|
||||
|
||||
@@ -3,14 +3,10 @@
|
||||
|
||||
package low
|
||||
|
||||
type IsParameter interface {
|
||||
GetName() *NodeReference[string]
|
||||
GetIn() *NodeReference[string]
|
||||
type SharedParameters interface {
|
||||
GetType() *NodeReference[string]
|
||||
GetDescription() *NodeReference[string]
|
||||
GetRequired() *NodeReference[bool]
|
||||
GetDeprecated() *NodeReference[bool]
|
||||
GetAllowEmptyValue() *NodeReference[bool]
|
||||
GetFormat() *NodeReference[string]
|
||||
GetStyle() *NodeReference[string]
|
||||
GetCollectionFormat() *NodeReference[string]
|
||||
@@ -30,8 +26,25 @@ type IsParameter interface {
|
||||
GetEnum() *NodeReference[[]ValueReference[string]]
|
||||
GetMultipleOf() *NodeReference[int]
|
||||
GetExample() *NodeReference[any]
|
||||
GetExamples() *NodeReference[any] // requires cast
|
||||
GetSchema() *NodeReference[any] // requires cast.
|
||||
GetExamples() *NodeReference[any] // requires cast
|
||||
GetContent() *NodeReference[any] // requires cast.
|
||||
GetItems() *NodeReference[any] // requires cast.
|
||||
}
|
||||
|
||||
type IsParameter interface {
|
||||
GetName() *NodeReference[string]
|
||||
GetIn() *NodeReference[string]
|
||||
SharedParameterHeader
|
||||
SharedParameters
|
||||
}
|
||||
|
||||
type SharedParameterHeader interface {
|
||||
GetRequired() *NodeReference[bool]
|
||||
GetAllowEmptyValue() *NodeReference[bool]
|
||||
}
|
||||
|
||||
type IsHeader interface {
|
||||
SharedParameters
|
||||
SharedParameterHeader
|
||||
}
|
||||
|
||||
@@ -82,6 +82,17 @@ func (n NodeReference[T]) IsEmpty() bool {
|
||||
return n.KeyNode == nil && n.ValueNode == nil
|
||||
}
|
||||
|
||||
func (n NodeReference[T]) IsReferenceNode() bool {
|
||||
for k := range n.KeyNode.Content {
|
||||
if k%2 == 0 {
|
||||
if n.KeyNode.Content[k].Value == "$ref" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateMapKey will return a string based on the line and column number of the node, e.g. 33:56 for line 33, col 56.
|
||||
func (n NodeReference[T]) GenerateMapKey() string {
|
||||
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
|
||||
|
||||
@@ -85,3 +85,100 @@ func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsHeader compliance methods
|
||||
|
||||
func (h *Header) GetType() *low.NodeReference[string] {
|
||||
return &h.Type
|
||||
}
|
||||
func (h *Header) GetDescription() *low.NodeReference[string] {
|
||||
return &h.Description
|
||||
}
|
||||
|
||||
func (h *Header) GetDeprecated() *low.NodeReference[bool] {
|
||||
// not implemented.
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetSchema() *low.NodeReference[any] {
|
||||
// not implemented.
|
||||
return &low.NodeReference[any]{}
|
||||
}
|
||||
func (h *Header) GetFormat() *low.NodeReference[string] {
|
||||
return &h.Format
|
||||
}
|
||||
func (h *Header) GetItems() *low.NodeReference[any] {
|
||||
i := low.NodeReference[any]{
|
||||
KeyNode: h.Items.KeyNode,
|
||||
ValueNode: h.Items.ValueNode,
|
||||
Value: h.Items.KeyNode,
|
||||
}
|
||||
return &i
|
||||
}
|
||||
func (h *Header) GetStyle() *low.NodeReference[string] {
|
||||
// not implemented.
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetCollectionFormat() *low.NodeReference[string] {
|
||||
return &h.CollectionFormat
|
||||
}
|
||||
func (h *Header) GetDefault() *low.NodeReference[any] {
|
||||
return &h.Default
|
||||
}
|
||||
func (h *Header) GetAllowReserved() *low.NodeReference[bool] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (h *Header) GetExplode() *low.NodeReference[bool] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (h *Header) GetMaximum() *low.NodeReference[int] {
|
||||
return &h.Maximum
|
||||
}
|
||||
func (h *Header) GetExclusiveMaximum() *low.NodeReference[bool] {
|
||||
return &h.ExclusiveMaximum
|
||||
}
|
||||
func (h *Header) GetMinimum() *low.NodeReference[int] {
|
||||
return &h.Minimum
|
||||
}
|
||||
func (h *Header) GetExclusiveMinimum() *low.NodeReference[bool] {
|
||||
return &h.ExclusiveMinimum
|
||||
}
|
||||
func (h *Header) GetMaxLength() *low.NodeReference[int] {
|
||||
return &h.MaxLength
|
||||
}
|
||||
func (h *Header) GetMinLength() *low.NodeReference[int] {
|
||||
return &h.MinLength
|
||||
}
|
||||
func (h *Header) GetPattern() *low.NodeReference[string] {
|
||||
return &h.Pattern
|
||||
}
|
||||
func (h *Header) GetMaxItems() *low.NodeReference[int] {
|
||||
return &h.MaxItems
|
||||
}
|
||||
func (h *Header) GetMinItems() *low.NodeReference[int] {
|
||||
return &h.MaxItems
|
||||
}
|
||||
func (h *Header) GetUniqueItems() *low.NodeReference[bool] {
|
||||
return &h.UniqueItems
|
||||
}
|
||||
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[string]] {
|
||||
return &h.Enum
|
||||
}
|
||||
func (h *Header) GetMultipleOf() *low.NodeReference[int] {
|
||||
return &h.MultipleOf
|
||||
}
|
||||
func (h *Header) GetExample() *low.NodeReference[any] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (h *Header) GetExamples() *low.NodeReference[any] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (h *Header) GetContent() *low.NodeReference[any] {
|
||||
return nil // not implemented
|
||||
}
|
||||
|
||||
func (h *Header) GetRequired() *low.NodeReference[bool] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (h *Header) GetAllowEmptyValue() *low.NodeReference[bool] {
|
||||
return nil // not implemented
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// Items is a low-level representation of a Swagger / OpenAPI 2 Items object.
|
||||
//
|
||||
// Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not
|
||||
// located in "body"
|
||||
// located in "body". Items, is actually identical to a Header, except it does not have description.
|
||||
// - https://swagger.io/specification/v2/#itemsObject
|
||||
type Items struct {
|
||||
Type low.NodeReference[string]
|
||||
@@ -120,3 +120,100 @@ func (i *Items) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsHeader compliance methods
|
||||
|
||||
func (i *Items) GetType() *low.NodeReference[string] {
|
||||
return &i.Type
|
||||
}
|
||||
func (i *Items) GetDescription() *low.NodeReference[string] {
|
||||
// not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Items) GetDeprecated() *low.NodeReference[bool] {
|
||||
// not implemented.
|
||||
return nil
|
||||
}
|
||||
func (i *Items) GetSchema() *low.NodeReference[any] {
|
||||
// not implemented.
|
||||
return &low.NodeReference[any]{}
|
||||
}
|
||||
func (i *Items) GetFormat() *low.NodeReference[string] {
|
||||
return &i.Format
|
||||
}
|
||||
func (i *Items) GetItems() *low.NodeReference[any] {
|
||||
k := low.NodeReference[any]{
|
||||
KeyNode: i.Items.KeyNode,
|
||||
ValueNode: i.Items.ValueNode,
|
||||
Value: i.Items.KeyNode,
|
||||
}
|
||||
return &k
|
||||
}
|
||||
func (i *Items) GetStyle() *low.NodeReference[string] {
|
||||
// not implemented.
|
||||
return nil
|
||||
}
|
||||
func (i *Items) GetCollectionFormat() *low.NodeReference[string] {
|
||||
return &i.CollectionFormat
|
||||
}
|
||||
func (i *Items) GetDefault() *low.NodeReference[any] {
|
||||
return &i.Default
|
||||
}
|
||||
func (i *Items) GetAllowReserved() *low.NodeReference[bool] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (i *Items) GetExplode() *low.NodeReference[bool] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (i *Items) GetMaximum() *low.NodeReference[int] {
|
||||
return &i.Maximum
|
||||
}
|
||||
func (i *Items) GetExclusiveMaximum() *low.NodeReference[bool] {
|
||||
return &i.ExclusiveMaximum
|
||||
}
|
||||
func (i *Items) GetMinimum() *low.NodeReference[int] {
|
||||
return &i.Minimum
|
||||
}
|
||||
func (i *Items) GetExclusiveMinimum() *low.NodeReference[bool] {
|
||||
return &i.ExclusiveMinimum
|
||||
}
|
||||
func (i *Items) GetMaxLength() *low.NodeReference[int] {
|
||||
return &i.MaxLength
|
||||
}
|
||||
func (i *Items) GetMinLength() *low.NodeReference[int] {
|
||||
return &i.MinLength
|
||||
}
|
||||
func (i *Items) GetPattern() *low.NodeReference[string] {
|
||||
return &i.Pattern
|
||||
}
|
||||
func (i *Items) GetMaxItems() *low.NodeReference[int] {
|
||||
return &i.MaxItems
|
||||
}
|
||||
func (i *Items) GetMinItems() *low.NodeReference[int] {
|
||||
return &i.MaxItems
|
||||
}
|
||||
func (i *Items) GetUniqueItems() *low.NodeReference[bool] {
|
||||
return &i.UniqueItems
|
||||
}
|
||||
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[string]] {
|
||||
return &i.Enum
|
||||
}
|
||||
func (i *Items) GetMultipleOf() *low.NodeReference[int] {
|
||||
return &i.MultipleOf
|
||||
}
|
||||
func (i *Items) GetExample() *low.NodeReference[any] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (i *Items) GetExamples() *low.NodeReference[any] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (i *Items) GetContent() *low.NodeReference[any] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (i *Items) GetAllowEmptyValue() *low.NodeReference[bool] {
|
||||
return nil // not implemented, not even a property... damn you swagger.
|
||||
}
|
||||
func (i *Items) GetRequired() *low.NodeReference[bool] {
|
||||
return nil // not implemented, not even a property... damn you swagger.
|
||||
}
|
||||
|
||||
@@ -99,4 +99,5 @@ const (
|
||||
CollectionFormatLabel = "collectionFormat"
|
||||
AllowReservedLabel = "allowReserved"
|
||||
ExplodeLabel = "explode"
|
||||
ContentTypeLabel = "contentType"
|
||||
)
|
||||
|
||||
@@ -52,19 +52,19 @@ func (h *Header) Hash() [32]byte {
|
||||
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)))))
|
||||
f = append(f, fmt.Sprint(h.Required.Value))
|
||||
f = append(f, fmt.Sprint(h.Deprecated.Value))
|
||||
f = append(f, 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)))))
|
||||
f = append(f, fmt.Sprint(h.Explode.Value))
|
||||
f = append(f, 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)))))
|
||||
f = append(f, fmt.Sprint(h.Example.Value))
|
||||
}
|
||||
if len(h.Examples.Value) > 0 {
|
||||
for k := range h.Examples.Value {
|
||||
@@ -126,3 +126,105 @@ func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsHeader compliance methods.
|
||||
|
||||
func (h *Header) GetType() *low.NodeReference[string] {
|
||||
return nil // not implemented
|
||||
}
|
||||
func (h *Header) GetDescription() *low.NodeReference[string] {
|
||||
return &h.Description
|
||||
}
|
||||
func (h *Header) GetRequired() *low.NodeReference[bool] {
|
||||
return &h.Required
|
||||
}
|
||||
func (h *Header) GetDeprecated() *low.NodeReference[bool] {
|
||||
return &h.Deprecated
|
||||
}
|
||||
func (h *Header) GetAllowEmptyValue() *low.NodeReference[bool] {
|
||||
return &h.AllowEmptyValue
|
||||
}
|
||||
func (h *Header) GetSchema() *low.NodeReference[any] {
|
||||
i := low.NodeReference[any]{
|
||||
KeyNode: h.Schema.KeyNode,
|
||||
ValueNode: h.Schema.ValueNode,
|
||||
Value: h.Schema.KeyNode,
|
||||
}
|
||||
return &i
|
||||
}
|
||||
func (h *Header) GetFormat() *low.NodeReference[string] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetItems() *low.NodeReference[any] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetStyle() *low.NodeReference[string] {
|
||||
return &h.Style
|
||||
}
|
||||
func (h *Header) GetCollectionFormat() *low.NodeReference[string] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetDefault() *low.NodeReference[any] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetAllowReserved() *low.NodeReference[bool] {
|
||||
return &h.AllowReserved
|
||||
}
|
||||
func (h *Header) GetExplode() *low.NodeReference[bool] {
|
||||
return &h.Explode
|
||||
}
|
||||
func (h *Header) GetMaximum() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetExclusiveMaximum() *low.NodeReference[bool] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetMinimum() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetExclusiveMinimum() *low.NodeReference[bool] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetMaxLength() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetMinLength() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetPattern() *low.NodeReference[string] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetMaxItems() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetMinItems() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetUniqueItems() *low.NodeReference[bool] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[string]] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetMultipleOf() *low.NodeReference[int] {
|
||||
return nil
|
||||
}
|
||||
func (h *Header) GetExample() *low.NodeReference[any] {
|
||||
return &h.Example
|
||||
}
|
||||
func (h *Header) GetExamples() *low.NodeReference[any] {
|
||||
i := low.NodeReference[any]{
|
||||
KeyNode: h.Examples.KeyNode,
|
||||
ValueNode: h.Examples.ValueNode,
|
||||
Value: h.Examples.KeyNode,
|
||||
}
|
||||
return &i
|
||||
}
|
||||
func (h *Header) GetContent() *low.NodeReference[any] {
|
||||
c := low.NodeReference[any]{
|
||||
KeyNode: h.Content.KeyNode,
|
||||
ValueNode: h.Content.ValueNode,
|
||||
Value: h.Content.Value,
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
@@ -117,6 +117,8 @@ func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool {
|
||||
// CheckPropertyAdditionOrRemoval
|
||||
// CheckForModification
|
||||
func CheckProperties(properties []*PropertyCheck) {
|
||||
|
||||
// todo: make this async to really speed things up.
|
||||
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)
|
||||
|
||||
@@ -37,7 +37,7 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges {
|
||||
dc := new(DiscriminatorChanges)
|
||||
var changes []*Change
|
||||
var props []*PropertyCheck
|
||||
var mapping []*Change
|
||||
var mappingChanges []*Change
|
||||
|
||||
// Name (breaking change)
|
||||
props = append(props, &PropertyCheck{
|
||||
@@ -59,11 +59,11 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges {
|
||||
|
||||
// check for removals, modifications and moves
|
||||
for i := range lMap {
|
||||
CheckForObjectAdditionOrRemoval[string](lMap, rMap, i, &mapping, false, true)
|
||||
CheckForObjectAdditionOrRemoval[string](lMap, rMap, i, &mappingChanges, false, true)
|
||||
// if the existing tag exists, let's check it.
|
||||
if rMap[i] != nil {
|
||||
if lMap[i].Value != rMap[i].Value {
|
||||
CreateChange(&mapping, Modified, i, lMap[i].GetValueNode(),
|
||||
CreateChange(&mappingChanges, Modified, i, lMap[i].GetValueNode(),
|
||||
rMap[i].GetValueNode(), true, lMap[i].GetValue(), rMap[i].GetValue())
|
||||
}
|
||||
}
|
||||
@@ -71,13 +71,13 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges {
|
||||
|
||||
for i := range rMap {
|
||||
if lMap[i] == nil {
|
||||
CreateChange(&mapping, ObjectAdded, i, nil,
|
||||
CreateChange(&mappingChanges, ObjectAdded, i, nil,
|
||||
rMap[i].GetValueNode(), false, nil, rMap[i].GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
dc.Changes = changes
|
||||
dc.MappingChanges = mapping
|
||||
dc.MappingChanges = mappingChanges
|
||||
if dc.TotalChanges() <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,81 @@
|
||||
|
||||
package what_changed
|
||||
|
||||
import v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
||||
|
||||
type EncodingChanges struct {
|
||||
ParameterChanges
|
||||
HeaderChanges *HeaderChanges
|
||||
ParameterChanges
|
||||
HeaderChanges map[string]*HeaderChanges
|
||||
}
|
||||
|
||||
func (e *EncodingChanges) TotalChanges() int {
|
||||
c := e.PropertyChanges.TotalChanges()
|
||||
if e.HeaderChanges != nil {
|
||||
for i := range e.HeaderChanges {
|
||||
c += e.HeaderChanges[i].TotalChanges()
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *EncodingChanges) TotalBreakingChanges() int {
|
||||
c := e.PropertyChanges.TotalBreakingChanges()
|
||||
if e.HeaderChanges != nil {
|
||||
for i := range e.HeaderChanges {
|
||||
c += e.HeaderChanges[i].TotalBreakingChanges()
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func CompareEncoding(l, r *v3.Encoding) *EncodingChanges {
|
||||
|
||||
var changes []*Change
|
||||
var props []*PropertyCheck
|
||||
|
||||
// ContentType
|
||||
props = append(props, &PropertyCheck{
|
||||
LeftNode: l.ContentType.ValueNode,
|
||||
RightNode: r.ContentType.ValueNode,
|
||||
Label: v3.ContentTypeLabel,
|
||||
Changes: &changes,
|
||||
Breaking: true,
|
||||
Original: l,
|
||||
New: r,
|
||||
})
|
||||
|
||||
// Explode
|
||||
props = append(props, &PropertyCheck{
|
||||
LeftNode: l.Explode.ValueNode,
|
||||
RightNode: r.Explode.ValueNode,
|
||||
Label: v3.ExplodeLabel,
|
||||
Changes: &changes,
|
||||
Breaking: true,
|
||||
Original: l,
|
||||
New: r,
|
||||
})
|
||||
|
||||
// AllowReserved
|
||||
props = append(props, &PropertyCheck{
|
||||
LeftNode: l.AllowReserved.ValueNode,
|
||||
RightNode: r.AllowReserved.ValueNode,
|
||||
Label: v3.AllowReservedLabel,
|
||||
Changes: &changes,
|
||||
Breaking: false,
|
||||
Original: l,
|
||||
New: r,
|
||||
})
|
||||
|
||||
// check everything.
|
||||
CheckProperties(props)
|
||||
|
||||
ec := new(EncodingChanges)
|
||||
ec.Changes = changes
|
||||
|
||||
// headers
|
||||
ec.HeaderChanges = CheckMapForChanges(l.Headers.Value, r.Headers.Value, &changes, v3.HeadersLabel, CompareHeadersV3)
|
||||
if ec.TotalChanges() <= 0 {
|
||||
return nil
|
||||
}
|
||||
return ec
|
||||
}
|
||||
|
||||
@@ -3,10 +3,90 @@
|
||||
|
||||
package what_changed
|
||||
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low/v3"
|
||||
)
|
||||
|
||||
type MediaTypeChanges struct {
|
||||
PropertyChanges
|
||||
SchemaChanges *SchemaChanges
|
||||
ExtensionChanges *ExtensionChanges
|
||||
ExampleChanges map[string]*ExampleChanges
|
||||
EncodingChanges *EncodingChanges
|
||||
EncodingChanges map[string]*EncodingChanges
|
||||
}
|
||||
|
||||
func (m *MediaTypeChanges) TotalChanges() int {
|
||||
c := m.PropertyChanges.TotalChanges()
|
||||
for k := range m.ExampleChanges {
|
||||
c += m.ExampleChanges[k].TotalChanges()
|
||||
}
|
||||
if m.SchemaChanges != nil {
|
||||
c += m.SchemaChanges.TotalChanges()
|
||||
}
|
||||
if len(m.EncodingChanges) > 0 {
|
||||
for i := range m.EncodingChanges {
|
||||
c += m.EncodingChanges[i].TotalChanges()
|
||||
}
|
||||
}
|
||||
if m.ExtensionChanges != nil {
|
||||
c += m.ExtensionChanges.TotalChanges()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (m *MediaTypeChanges) TotalBreakingChanges() int {
|
||||
c := m.PropertyChanges.TotalBreakingChanges()
|
||||
for k := range m.ExampleChanges {
|
||||
c += m.ExampleChanges[k].TotalBreakingChanges()
|
||||
}
|
||||
if m.SchemaChanges != nil {
|
||||
c += m.SchemaChanges.TotalBreakingChanges()
|
||||
}
|
||||
if len(m.EncodingChanges) > 0 {
|
||||
for i := range m.EncodingChanges {
|
||||
c += m.EncodingChanges[i].TotalBreakingChanges()
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func CompareMediaTypes(l, r *v3.MediaType) *MediaTypeChanges {
|
||||
|
||||
var props []*PropertyCheck
|
||||
var changes []*Change
|
||||
|
||||
mc := new(MediaTypeChanges)
|
||||
|
||||
// Example
|
||||
addPropertyCheck(&props, l.Example.ValueNode, r.Example.ValueNode,
|
||||
l.Example.Value, r.Example.Value, &changes, v3.ExampleLabel, false)
|
||||
|
||||
CheckProperties(props)
|
||||
mc.Changes = changes
|
||||
|
||||
// schema
|
||||
if !l.Schema.IsEmpty() && !r.Schema.IsEmpty() {
|
||||
mc.SchemaChanges = CompareSchemas(l.Schema.Value, r.Schema.Value)
|
||||
}
|
||||
if !l.Schema.IsEmpty() && r.Schema.IsEmpty() {
|
||||
CreateChange(&changes, ObjectRemoved, v3.SchemaLabel, l.Schema.ValueNode,
|
||||
nil, true, l.Schema.Value, nil)
|
||||
}
|
||||
if l.Schema.IsEmpty() && !r.Schema.IsEmpty() {
|
||||
CreateChange(&changes, ObjectAdded, v3.SchemaLabel, nil,
|
||||
r.Schema.ValueNode, true, nil, r.Schema.Value)
|
||||
}
|
||||
|
||||
// examples
|
||||
mc.ExampleChanges = CheckMapForChanges(l.Examples.Value, r.Examples.Value,
|
||||
&changes, v3.ExamplesLabel, CompareExamples)
|
||||
|
||||
// encoding
|
||||
mc.EncodingChanges = CheckMapForChanges(l.Encoding.Value, r.Encoding.Value,
|
||||
&changes, v3.EncodingLabel, CompareEncoding)
|
||||
|
||||
if mc.TotalChanges() <= 0 {
|
||||
return nil
|
||||
}
|
||||
return mc
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ParameterChanges struct {
|
||||
@@ -19,7 +21,7 @@ type ParameterChanges struct {
|
||||
ExtensionChanges *ExtensionChanges
|
||||
|
||||
// V2 change types
|
||||
// ItemsChanges
|
||||
ItemsChanges *ItemsChanges
|
||||
|
||||
// v3 change types
|
||||
ExampleChanges map[string]*ExampleChanges
|
||||
@@ -84,6 +86,10 @@ func addOpenAPIParameterProperties(left, right low.IsParameter, changes *[]*Chan
|
||||
addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode,
|
||||
left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false)
|
||||
|
||||
// example
|
||||
addPropertyCheck(&props, left.GetExample().ValueNode, right.GetExample().ValueNode,
|
||||
left.GetExample(), right.GetExample(), changes, v3.ExampleLabel, false)
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
@@ -206,10 +212,46 @@ func CompareParameters(l, r any) *ParameterChanges {
|
||||
rSchema = rParam.Schema.Value
|
||||
}
|
||||
|
||||
// todo: items
|
||||
// todo: default
|
||||
// todo: enums
|
||||
// items
|
||||
if !lParam.Items.IsEmpty() && !rParam.Items.IsEmpty() {
|
||||
if lParam.Items.Value.Hash() != rParam.Items.Value.Hash() {
|
||||
pc.ItemsChanges = CompareItems(lParam.Items.Value, rParam.Items.Value)
|
||||
}
|
||||
}
|
||||
if lParam.Items.IsEmpty() && !rParam.Items.IsEmpty() {
|
||||
CreateChange(&changes, ObjectAdded, v3.ItemsLabel,
|
||||
nil, rParam.Items.ValueNode, true, nil,
|
||||
rParam.Items.Value)
|
||||
}
|
||||
if !lParam.Items.IsEmpty() && rParam.Items.IsEmpty() {
|
||||
CreateChange(&changes, ObjectRemoved, v3.ItemsLabel,
|
||||
lParam.Items.ValueNode, nil, true, lParam.Items.Value,
|
||||
nil)
|
||||
}
|
||||
|
||||
// default
|
||||
if !lParam.Default.IsEmpty() && !rParam.Default.IsEmpty() {
|
||||
if low.GenerateHashString(lParam.Default.Value) != low.GenerateHashString(lParam.Default.Value) {
|
||||
CreateChange(&changes, Modified, v3.DefaultLabel,
|
||||
lParam.Items.ValueNode, rParam.Items.ValueNode, true, lParam.Items.Value,
|
||||
rParam.Items.ValueNode)
|
||||
}
|
||||
}
|
||||
if lParam.Default.IsEmpty() && !rParam.Default.IsEmpty() {
|
||||
CreateChange(&changes, ObjectAdded, v3.DefaultLabel,
|
||||
nil, rParam.Default.ValueNode, true, nil,
|
||||
rParam.Default.Value)
|
||||
}
|
||||
if !lParam.Default.IsEmpty() && rParam.Items.IsEmpty() {
|
||||
CreateChange(&changes, ObjectRemoved, v3.ItemsLabel,
|
||||
lParam.Items.ValueNode, nil, true, lParam.Items.Value,
|
||||
nil)
|
||||
}
|
||||
|
||||
// enum
|
||||
if len(lParam.Enum.Value) > 0 || len(rParam.Enum.Value) > 0 {
|
||||
ExtractStringValueSliceChanges(lParam.Enum.Value, rParam.Enum.Value, &changes, v3.EnumLabel)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAPI
|
||||
@@ -235,28 +277,10 @@ func CompareParameters(l, r any) *ParameterChanges {
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
}
|
||||
checkParameterExample(lParam.Example, rParam.Example, changes)
|
||||
|
||||
// examples
|
||||
checkParameterExamples(lParam, rParam, changes, pc)
|
||||
CheckMapForChanges(lParam.Examples.Value, rParam.Examples.Value, &changes, v3.ExamplesLabel, CompareExamples)
|
||||
|
||||
// todo: content
|
||||
|
||||
@@ -287,54 +311,125 @@ func CompareParameters(l, r any) *ParameterChanges {
|
||||
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]
|
||||
func ExtractStringValueSliceChanges(lParam, rParam []low.ValueReference[string], changes *[]*Change, label string) {
|
||||
lKeys := make([]string, len(lParam))
|
||||
rKeys := make([]string, len(rParam))
|
||||
lValues := make(map[string]low.ValueReference[string])
|
||||
rValues := make(map[string]low.ValueReference[string])
|
||||
for i := range lParam {
|
||||
lKeys[i] = strings.ToLower(lParam[i].Value)
|
||||
lValues[lKeys[i]] = lParam[i]
|
||||
}
|
||||
for i := range rParam {
|
||||
rKeys[i] = strings.ToLower(rParam[i].Value)
|
||||
rValues[lKeys[i]] = rParam[i]
|
||||
}
|
||||
sort.Strings(lKeys)
|
||||
sort.Strings(rKeys)
|
||||
|
||||
for i := range lKeys {
|
||||
if i < len(rKeys) {
|
||||
if lKeys[i] != rKeys[i] {
|
||||
CreateChange(changes, Modified, label,
|
||||
lValues[lKeys[i]].ValueNode,
|
||||
rValues[rKeys[i]].ValueNode,
|
||||
true,
|
||||
lValues[lKeys[i]].Value,
|
||||
rValues[rKeys[i]].ValueNode)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if i >= len(rKeys) {
|
||||
CreateChange(changes, PropertyRemoved, label,
|
||||
lValues[lKeys[i]].ValueNode,
|
||||
nil,
|
||||
true,
|
||||
lValues[lKeys[i]].Value,
|
||||
nil)
|
||||
}
|
||||
}
|
||||
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]
|
||||
for i := range rKeys {
|
||||
if i >= len(lKeys) {
|
||||
CreateChange(changes, PropertyAdded, label,
|
||||
nil,
|
||||
rValues[rKeys[i]].ValueNode,
|
||||
false,
|
||||
nil,
|
||||
rValues[rKeys[i]].ValueNode)
|
||||
}
|
||||
}
|
||||
expChanges := make(map[string]*ExampleChanges)
|
||||
}
|
||||
|
||||
func checkParameterExample(expLeft, expRight low.NodeReference[any], changes []*Change) {
|
||||
if !expLeft.IsEmpty() && !expRight.IsEmpty() {
|
||||
if low.GenerateHashString(expLeft.GetValue()) != low.GenerateHashString(expRight.GetValue()) {
|
||||
CreateChange(&changes, Modified, v3.ExampleLabel,
|
||||
expLeft.GetValueNode(), expRight.GetValueNode(), false,
|
||||
expLeft.GetValue(), expRight.GetValue())
|
||||
}
|
||||
}
|
||||
if expLeft.Value == nil && expRight.Value != nil {
|
||||
CreateChange(&changes, PropertyAdded, v3.ExampleLabel,
|
||||
nil, expRight.GetValueNode(), false,
|
||||
nil, expRight.GetValue())
|
||||
|
||||
}
|
||||
if expLeft.Value != nil && expRight.Value == nil {
|
||||
CreateChange(&changes, PropertyRemoved, v3.ExampleLabel,
|
||||
expLeft.GetValueNode(), nil, false,
|
||||
expLeft.GetValue(), nil)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func CheckMapForChanges[T any, R any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T],
|
||||
changes *[]*Change, label string, compareFunc func(l, r T) R) map[string]R {
|
||||
|
||||
lHashes := make(map[string]string)
|
||||
rHashes := make(map[string]string)
|
||||
lValues := make(map[string]low.ValueReference[T])
|
||||
rValues := make(map[string]low.ValueReference[T])
|
||||
|
||||
for k := range expLeft {
|
||||
lHashes[k.Value] = fmt.Sprintf("%x", low.GenerateHashString(expLeft[k].Value))
|
||||
lValues[k.Value] = expLeft[k]
|
||||
}
|
||||
|
||||
for k := range expRight {
|
||||
rHashes[k.Value] = fmt.Sprintf("%x", low.GenerateHashString(expRight[k].Value))
|
||||
rValues[k.Value] = expRight[k]
|
||||
}
|
||||
|
||||
expChanges := make(map[string]R)
|
||||
|
||||
// check left example hashes
|
||||
for k := range lExpHashes {
|
||||
rhash := rExpHashes[k]
|
||||
for k := range lHashes {
|
||||
rhash := rHashes[k]
|
||||
if rhash == "" {
|
||||
CreateChange(&changes, ObjectRemoved, v3.ExamplesLabel,
|
||||
lExpValues[k].GetValueNode(), nil, false,
|
||||
lExpValues[k].GetValue(), nil)
|
||||
CreateChange(changes, ObjectRemoved, label,
|
||||
lValues[k].GetValueNode(), nil, false,
|
||||
lValues[k].GetValue(), nil)
|
||||
continue
|
||||
}
|
||||
if lExpHashes[k] == rExpHashes[k] {
|
||||
if lHashes[k] == rHashes[k] {
|
||||
continue
|
||||
}
|
||||
expChanges[k] = CompareExamples(lExpValues[k].Value, rExpValues[k].Value)
|
||||
// run comparison.
|
||||
expChanges[k] = compareFunc(lValues[k].Value, rValues[k].Value)
|
||||
}
|
||||
|
||||
//check right example hashes
|
||||
for k := range rExpHashes {
|
||||
lhash := lExpHashes[k]
|
||||
for k := range rHashes {
|
||||
lhash := lHashes[k]
|
||||
if lhash == "" {
|
||||
CreateChange(&changes, ObjectAdded, v3.ExamplesLabel,
|
||||
nil, lExpValues[k].GetValueNode(), false,
|
||||
nil, lExpValues[k].GetValue())
|
||||
CreateChange(changes, ObjectAdded, v3.ExamplesLabel,
|
||||
nil, lValues[k].GetValueNode(), false,
|
||||
nil, lValues[k].GetValue())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(expChanges) > 0 {
|
||||
pc.ExampleChanges = expChanges
|
||||
}
|
||||
return expChanges
|
||||
}
|
||||
|
||||
func checkParameterContent(lParam *v3.Parameter, rParam *v3.Parameter, changes []*Change, pc *ParameterChanges) {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
package what_changed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
@@ -150,6 +152,25 @@ func test_BuildDoc(l, r string) (*v3.Document, *v3.Document) {
|
||||
return leftDoc, rightDoc
|
||||
}
|
||||
|
||||
func test_BuildDocv2(l, r string) (*v2.Swagger, *v2.Swagger) {
|
||||
|
||||
leftInfo, _ := datamodel.ExtractSpecInfo([]byte(l))
|
||||
rightInfo, _ := datamodel.ExtractSpecInfo([]byte(r))
|
||||
|
||||
var err []error
|
||||
var leftDoc, rightDoc *v2.Swagger
|
||||
leftDoc, err = v2.CreateDocument(leftInfo)
|
||||
rightDoc, err = v2.CreateDocument(rightInfo)
|
||||
|
||||
if len(err) > 0 {
|
||||
for i := range err {
|
||||
fmt.Printf("error: %v\n", err[i])
|
||||
}
|
||||
panic("failed to create doc")
|
||||
}
|
||||
return leftDoc, rightDoc
|
||||
}
|
||||
|
||||
func TestCompareSchemas_RefIgnore(t *testing.T) {
|
||||
left := `components:
|
||||
schemas:
|
||||
|
||||
Reference in New Issue
Block a user