mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 12:37:49 +00:00
Replacing extensions hash code **breaking change**
This is a large update, I realized that extensions are not being hashed correctly, and because I have the same code everywhere, it means running back through the stack and cleaning up the invalid code that will break if multiple extensions are used in different positions in the raw spec. At the same time, I realized that the v2 model has the same primitive/enum issues that are part cleaned up in v3. This is a breaking changhe because enums are now []any and not []string, as well as primitives for bool, int etc are all pointers now instead of the copied values. This will break any consumers.
This commit is contained in:
@@ -28,7 +28,7 @@ type Header struct {
|
||||
MaxItems int
|
||||
MinItems int
|
||||
UniqueItems bool
|
||||
Enum []string
|
||||
Enum []any
|
||||
MultipleOf int
|
||||
Extensions map[string]any
|
||||
low *low.Header
|
||||
@@ -88,7 +88,7 @@ func NewHeader(header *low.Header) *Header {
|
||||
h.UniqueItems = header.UniqueItems.IsEmpty()
|
||||
}
|
||||
if !header.Enum.IsEmpty() {
|
||||
var enums []string
|
||||
var enums []any
|
||||
for e := range header.Enum.Value {
|
||||
enums = append(enums, header.Enum.Value[e].Value)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
package v2
|
||||
|
||||
import low "github.com/pb33f/libopenapi/datamodel/low/v2"
|
||||
import (
|
||||
low "github.com/pb33f/libopenapi/datamodel/low/v2"
|
||||
)
|
||||
|
||||
// Items is a high-level representation of a Swagger / OpenAPI 2 Items object, backed by a low level one.
|
||||
// Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not
|
||||
@@ -25,7 +27,7 @@ type Items struct {
|
||||
MaxItems int
|
||||
MinItems int
|
||||
UniqueItems bool
|
||||
Enum []string
|
||||
Enum []any
|
||||
MultipleOf int
|
||||
low *low.Items
|
||||
}
|
||||
@@ -80,7 +82,7 @@ func NewItems(items *low.Items) *Items {
|
||||
i.UniqueItems = items.UniqueItems.Value
|
||||
}
|
||||
if !items.Enum.IsEmpty() {
|
||||
var enums []string
|
||||
var enums []any
|
||||
for e := range items.Enum.Value {
|
||||
enums = append(enums, items.Enum.Value[e].Value)
|
||||
}
|
||||
|
||||
@@ -46,24 +46,24 @@ type Parameter struct {
|
||||
Type string
|
||||
Format string
|
||||
Description string
|
||||
Required bool
|
||||
AllowEmptyValue bool
|
||||
Required *bool
|
||||
AllowEmptyValue *bool
|
||||
Schema *base.SchemaProxy
|
||||
Items *Items
|
||||
CollectionFormat string
|
||||
Default any
|
||||
Maximum int
|
||||
ExclusiveMaximum bool
|
||||
Minimum int
|
||||
ExclusiveMinimum bool
|
||||
MaxLength int
|
||||
MinLength int
|
||||
Maximum *int
|
||||
ExclusiveMaximum *bool
|
||||
Minimum *int
|
||||
ExclusiveMinimum *bool
|
||||
MaxLength *int
|
||||
MinLength *int
|
||||
Pattern string
|
||||
MaxItems int
|
||||
MinItems int
|
||||
UniqueItems bool
|
||||
Enum []string
|
||||
MultipleOf int
|
||||
MaxItems *int
|
||||
MinItems *int
|
||||
UniqueItems *bool
|
||||
Enum []any
|
||||
MultipleOf *int
|
||||
Extensions map[string]any
|
||||
low *low.Parameter
|
||||
}
|
||||
@@ -89,10 +89,10 @@ func NewParameter(parameter *low.Parameter) *Parameter {
|
||||
p.Description = parameter.Description.Value
|
||||
}
|
||||
if !parameter.Required.IsEmpty() {
|
||||
p.Required = parameter.Required.Value
|
||||
p.Required = ¶meter.Required.Value
|
||||
}
|
||||
if !parameter.AllowEmptyValue.IsEmpty() {
|
||||
p.AllowEmptyValue = parameter.AllowEmptyValue.Value
|
||||
p.AllowEmptyValue = ¶meter.AllowEmptyValue.Value
|
||||
}
|
||||
if !parameter.Schema.IsEmpty() {
|
||||
p.Schema = base.NewSchemaProxy(¶meter.Schema)
|
||||
@@ -107,44 +107,44 @@ func NewParameter(parameter *low.Parameter) *Parameter {
|
||||
p.Default = parameter.Default.Value
|
||||
}
|
||||
if !parameter.Maximum.IsEmpty() {
|
||||
p.Maximum = parameter.Maximum.Value
|
||||
p.Maximum = ¶meter.Maximum.Value
|
||||
}
|
||||
if !parameter.ExclusiveMaximum.IsEmpty() {
|
||||
p.ExclusiveMaximum = parameter.ExclusiveMaximum.Value
|
||||
p.ExclusiveMaximum = ¶meter.ExclusiveMaximum.Value
|
||||
}
|
||||
if !parameter.Minimum.IsEmpty() {
|
||||
p.Minimum = parameter.Minimum.Value
|
||||
p.Minimum = ¶meter.Minimum.Value
|
||||
}
|
||||
if !parameter.ExclusiveMinimum.IsEmpty() {
|
||||
p.ExclusiveMinimum = parameter.ExclusiveMinimum.Value
|
||||
p.ExclusiveMinimum = ¶meter.ExclusiveMinimum.Value
|
||||
}
|
||||
if !parameter.MaxLength.IsEmpty() {
|
||||
p.MaxLength = parameter.MaxLength.Value
|
||||
p.MaxLength = ¶meter.MaxLength.Value
|
||||
}
|
||||
if !parameter.MinLength.IsEmpty() {
|
||||
p.MinLength = parameter.MinLength.Value
|
||||
p.MinLength = ¶meter.MinLength.Value
|
||||
}
|
||||
if !parameter.Pattern.IsEmpty() {
|
||||
p.Pattern = parameter.Pattern.Value
|
||||
}
|
||||
if !parameter.MinItems.IsEmpty() {
|
||||
p.MinItems = parameter.MinItems.Value
|
||||
p.MinItems = ¶meter.MinItems.Value
|
||||
}
|
||||
if !parameter.MaxItems.IsEmpty() {
|
||||
p.MaxItems = parameter.MaxItems.Value
|
||||
p.MaxItems = ¶meter.MaxItems.Value
|
||||
}
|
||||
if !parameter.UniqueItems.IsEmpty() {
|
||||
p.UniqueItems = parameter.UniqueItems.Value
|
||||
p.UniqueItems = ¶meter.UniqueItems.Value
|
||||
}
|
||||
if !parameter.Enum.IsEmpty() {
|
||||
var enums []string
|
||||
var enums []any
|
||||
for e := range parameter.Enum.Value {
|
||||
enums = append(enums, parameter.Enum.Value[e].Value)
|
||||
}
|
||||
p.Enum = enums
|
||||
}
|
||||
if !parameter.MultipleOf.IsEmpty() {
|
||||
p.MultipleOf = parameter.MultipleOf.Value
|
||||
p.MultipleOf = ¶meter.MultipleOf.Value
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -222,19 +222,19 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
|
||||
assert.Equal(t, "petId", upload.Parameters[0].Name)
|
||||
assert.Equal(t, "path", upload.Parameters[0].In)
|
||||
assert.Equal(t, "ID of pet to update", upload.Parameters[0].Description)
|
||||
assert.True(t, upload.Parameters[0].Required)
|
||||
assert.True(t, *upload.Parameters[0].Required)
|
||||
assert.Equal(t, "integer", upload.Parameters[0].Type)
|
||||
assert.Equal(t, "int64", upload.Parameters[0].Format)
|
||||
assert.True(t, upload.Parameters[0].ExclusiveMaximum)
|
||||
assert.True(t, upload.Parameters[0].ExclusiveMinimum)
|
||||
assert.Equal(t, 2, upload.Parameters[0].MaxLength)
|
||||
assert.Equal(t, 1, upload.Parameters[0].MinLength)
|
||||
assert.Equal(t, 1, upload.Parameters[0].Minimum)
|
||||
assert.Equal(t, 5, upload.Parameters[0].Maximum)
|
||||
assert.True(t, *upload.Parameters[0].ExclusiveMaximum)
|
||||
assert.True(t, *upload.Parameters[0].ExclusiveMinimum)
|
||||
assert.Equal(t, 2, *upload.Parameters[0].MaxLength)
|
||||
assert.Equal(t, 1, *upload.Parameters[0].MinLength)
|
||||
assert.Equal(t, 1, *upload.Parameters[0].Minimum)
|
||||
assert.Equal(t, 5, *upload.Parameters[0].Maximum)
|
||||
assert.Equal(t, "hi!", upload.Parameters[0].Pattern)
|
||||
assert.Equal(t, 1, upload.Parameters[0].MinItems)
|
||||
assert.Equal(t, 20, upload.Parameters[0].MaxItems)
|
||||
assert.True(t, upload.Parameters[0].UniqueItems)
|
||||
assert.Equal(t, 1, *upload.Parameters[0].MinItems)
|
||||
assert.Equal(t, 20, *upload.Parameters[0].MaxItems)
|
||||
assert.True(t, *upload.Parameters[0].UniqueItems)
|
||||
assert.Len(t, upload.Parameters[0].Enum, 2)
|
||||
assert.Equal(t, "hello", upload.Parameters[0].Enum[0])
|
||||
def := upload.Parameters[0].Default.(map[string]interface{})
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Contact represents a low-level representation of the Contact definitions found at
|
||||
@@ -23,3 +25,18 @@ func (c *Contact) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
// not implemented.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the Contact object
|
||||
func (c *Contact) Hash() [32]byte {
|
||||
var f []string
|
||||
if !c.Name.IsEmpty() {
|
||||
f = append(f, c.Name.Value)
|
||||
}
|
||||
if !c.URL.IsEmpty() {
|
||||
f = append(f, c.URL.Value)
|
||||
}
|
||||
if !c.Email.IsEmpty() {
|
||||
f = append(f, c.Email.Value)
|
||||
}
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
34
datamodel/low/base/contact_test.go
Normal file
34
datamodel/low/base/contact_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContact_Hash(t *testing.T) {
|
||||
|
||||
left := `url: https://pb33f.io
|
||||
description: the ranch
|
||||
email: buckaroo@pb33f.io`
|
||||
|
||||
right := `url: https://pb33f.io
|
||||
description: the ranch
|
||||
email: buckaroo@pb33f.io`
|
||||
|
||||
var lNode, rNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||
|
||||
// create low level objects
|
||||
var lDoc Contact
|
||||
var rDoc Contact
|
||||
_ = low.BuildModel(lNode.Content[0], &lDoc)
|
||||
_ = low.BuildModel(rNode.Content[0], &rDoc)
|
||||
|
||||
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -45,9 +46,14 @@ func (ex *Example) Hash() [32]byte {
|
||||
if ex.ExternalValue.Value != "" {
|
||||
f = append(f, ex.ExternalValue.Value)
|
||||
}
|
||||
keys := make([]string, len(ex.Extensions))
|
||||
z := 0
|
||||
for k := range ex.Extensions {
|
||||
f = append(f, fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(ex.Extensions[k].Value)))))
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(ex.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ package base
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -40,9 +42,17 @@ func (ex *ExternalDoc) GetExtensions() map[low.KeyReference[string]]low.ValueRef
|
||||
|
||||
func (ex *ExternalDoc) Hash() [32]byte {
|
||||
// calculate a hash from every property.
|
||||
d := []string{
|
||||
f := []string{
|
||||
ex.Description.Value,
|
||||
ex.URL.Value,
|
||||
}
|
||||
return sha256.Sum256([]byte(strings.Join(d, "|")))
|
||||
keys := make([]string, len(ex.Extensions))
|
||||
z := 0
|
||||
for k := range ex.Extensions {
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(ex.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Info represents a low-level Info object as defined by both OpenAPI 2 and OpenAPI 3.
|
||||
@@ -44,3 +48,36 @@ func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
i.License = lic
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the Info object
|
||||
func (i *Info) Hash() [32]byte {
|
||||
var f []string
|
||||
|
||||
if !i.Title.IsEmpty() {
|
||||
f = append(f, i.Title.Value)
|
||||
}
|
||||
if !i.Description.IsEmpty() {
|
||||
f = append(f, i.Description.Value)
|
||||
}
|
||||
if !i.TermsOfService.IsEmpty() {
|
||||
f = append(f, i.TermsOfService.Value)
|
||||
}
|
||||
if !i.Contact.IsEmpty() {
|
||||
f = append(f, low.GenerateHashString(i.Contact.Value))
|
||||
}
|
||||
if !i.License.IsEmpty() {
|
||||
f = append(f, low.GenerateHashString(i.License.Value))
|
||||
}
|
||||
if !i.Version.IsEmpty() {
|
||||
f = append(f, i.Version.Value)
|
||||
}
|
||||
keys := make([]string, len(i.Extensions))
|
||||
z := 0
|
||||
for k := range i.Extensions {
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(i.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
@@ -68,3 +68,45 @@ func TestLicense_Build(t *testing.T) {
|
||||
k := n.Build(nil, nil)
|
||||
assert.Nil(t, k)
|
||||
}
|
||||
|
||||
func TestInfo_Hash(t *testing.T) {
|
||||
|
||||
left := `title: princess b33f
|
||||
description: a thing
|
||||
termsOfService: https://pb33f.io
|
||||
x-princess: b33f
|
||||
contact:
|
||||
name: buckaroo
|
||||
url: https://pb33f.io
|
||||
license:
|
||||
name: magic beans
|
||||
version: 1.2.3
|
||||
x-b33f: princess`
|
||||
|
||||
right := `title: princess b33f
|
||||
description: a thing
|
||||
termsOfService: https://pb33f.io
|
||||
x-princess: b33f
|
||||
contact:
|
||||
name: buckaroo
|
||||
url: https://pb33f.io
|
||||
license:
|
||||
name: magic beans
|
||||
version: 1.2.3
|
||||
x-b33f: princess`
|
||||
|
||||
var lNode, rNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||
|
||||
// create low level objects
|
||||
var lDoc Info
|
||||
var rDoc Info
|
||||
_ = low.BuildModel(lNode.Content[0], &lDoc)
|
||||
_ = low.BuildModel(rNode.Content[0], &rDoc)
|
||||
_ = lDoc.Build(lNode.Content[0], nil)
|
||||
_ = rDoc.Build(rNode.Content[0], nil)
|
||||
|
||||
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// License is a low-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
|
||||
@@ -21,3 +23,15 @@ type License struct {
|
||||
func (l *License) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the License object
|
||||
func (l *License) Hash() [32]byte {
|
||||
var f []string
|
||||
if !l.Name.IsEmpty() {
|
||||
f = append(f, l.Name.Value)
|
||||
}
|
||||
if !l.URL.IsEmpty() {
|
||||
f = append(f, l.URL.Value)
|
||||
}
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
33
datamodel/low/base/license_test.go
Normal file
33
datamodel/low/base/license_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLicense_Hash(t *testing.T) {
|
||||
|
||||
left := `url: https://pb33f.io
|
||||
description: the ranch`
|
||||
|
||||
right := `url: https://pb33f.io
|
||||
description: the ranch`
|
||||
|
||||
var lNode, rNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||
|
||||
// create low level objects
|
||||
var lDoc License
|
||||
var rDoc License
|
||||
_ = low.BuildModel(lNode.Content[0], &lDoc)
|
||||
_ = low.BuildModel(rNode.Content[0], &rDoc)
|
||||
|
||||
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
|
||||
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func (s *Schema) Hash() [32]byte {
|
||||
d = append(d, fmt.Sprint(s.MinProperties.Value))
|
||||
}
|
||||
if !s.AdditionalProperties.IsEmpty() {
|
||||
d = append(d, fmt.Sprint(s.AdditionalProperties.Value))
|
||||
d = append(d, low.GenerateHashString(s.AdditionalProperties.Value))
|
||||
}
|
||||
if !s.Description.IsEmpty() {
|
||||
d = append(d, fmt.Sprint(s.Description.Value))
|
||||
@@ -185,7 +185,6 @@ func (s *Schema) Hash() [32]byte {
|
||||
if !s.ExclusiveMaximum.IsEmpty() && s.ExclusiveMaximum.Value.IsB() {
|
||||
d = append(d, fmt.Sprint(s.ExclusiveMaximum.Value.B))
|
||||
}
|
||||
|
||||
if !s.ExclusiveMinimum.IsEmpty() && s.ExclusiveMinimum.Value.IsA() {
|
||||
d = append(d, fmt.Sprint(s.ExclusiveMinimum.Value.A))
|
||||
}
|
||||
@@ -204,15 +203,28 @@ func (s *Schema) Hash() [32]byte {
|
||||
d = append(d, strings.Join(j, "|"))
|
||||
}
|
||||
|
||||
keys := make([]string, len(s.Required.Value))
|
||||
for i := range s.Required.Value {
|
||||
d = append(d, s.Required.Value[i].Value)
|
||||
keys[i] = s.Required.Value[i].Value
|
||||
}
|
||||
sort.Strings(keys)
|
||||
d = append(d, keys...)
|
||||
|
||||
keys = make([]string, len(s.Enum.Value))
|
||||
for i := range s.Enum.Value {
|
||||
keys[i] = fmt.Sprint(s.Enum.Value[i].Value)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
d = append(d, keys...)
|
||||
|
||||
for i := range s.Enum.Value {
|
||||
d = append(d, fmt.Sprint(s.Enum.Value[i].Value))
|
||||
}
|
||||
propertyKeys := make([]string, 0, len(s.Properties.Value))
|
||||
propertyKeys := make([]string, len(s.Properties.Value))
|
||||
z := 0
|
||||
for i := range s.Properties.Value {
|
||||
propertyKeys = append(propertyKeys, i.Value)
|
||||
propertyKeys[z] = i.Value
|
||||
z++
|
||||
}
|
||||
sort.Strings(propertyKeys)
|
||||
for k := range propertyKeys {
|
||||
@@ -233,15 +245,17 @@ func (s *Schema) Hash() [32]byte {
|
||||
|
||||
// hash polymorphic data
|
||||
if len(s.OneOf.Value) > 0 {
|
||||
oneOfKeys := make([]string, 0, len(s.OneOf.Value))
|
||||
oneOfKeys := make([]string, len(s.OneOf.Value))
|
||||
oneOfEntities := make(map[string]*Schema)
|
||||
z = 0
|
||||
for i := range s.OneOf.Value {
|
||||
g := s.OneOf.Value[i].Value
|
||||
if !g.IsSchemaReference() {
|
||||
k := g.Schema()
|
||||
r := low.GenerateHashString(k)
|
||||
oneOfEntities[r] = k
|
||||
oneOfKeys = append(oneOfKeys, r)
|
||||
oneOfKeys[z] = r
|
||||
z++
|
||||
}
|
||||
}
|
||||
sort.Strings(oneOfKeys)
|
||||
@@ -251,15 +265,17 @@ func (s *Schema) Hash() [32]byte {
|
||||
}
|
||||
|
||||
if len(s.AllOf.Value) > 0 {
|
||||
allOfKeys := make([]string, 0, len(s.AllOf.Value))
|
||||
allOfKeys := make([]string, len(s.AllOf.Value))
|
||||
allOfEntities := make(map[string]*Schema)
|
||||
z = 0
|
||||
for i := range s.AllOf.Value {
|
||||
g := s.AllOf.Value[i].Value
|
||||
if !g.IsSchemaReference() {
|
||||
k := g.Schema()
|
||||
r := low.GenerateHashString(k)
|
||||
allOfEntities[r] = k
|
||||
allOfKeys = append(allOfKeys, r)
|
||||
allOfKeys[z] = r
|
||||
z++
|
||||
}
|
||||
}
|
||||
sort.Strings(allOfKeys)
|
||||
@@ -269,15 +285,17 @@ func (s *Schema) Hash() [32]byte {
|
||||
}
|
||||
|
||||
if len(s.AnyOf.Value) > 0 {
|
||||
anyOfKeys := make([]string, 0, len(s.AnyOf.Value))
|
||||
anyOfKeys := make([]string, len(s.AnyOf.Value))
|
||||
anyOfEntities := make(map[string]*Schema)
|
||||
z = 0
|
||||
for i := range s.AnyOf.Value {
|
||||
g := s.AnyOf.Value[i].Value
|
||||
if !g.IsSchemaReference() {
|
||||
k := g.Schema()
|
||||
r := low.GenerateHashString(k)
|
||||
anyOfEntities[r] = k
|
||||
anyOfKeys = append(anyOfKeys, r)
|
||||
anyOfKeys[z] = r
|
||||
z++
|
||||
}
|
||||
}
|
||||
sort.Strings(anyOfKeys)
|
||||
@@ -287,15 +305,17 @@ func (s *Schema) Hash() [32]byte {
|
||||
}
|
||||
|
||||
if len(s.Not.Value) > 0 {
|
||||
notKeys := make([]string, 0, len(s.Not.Value))
|
||||
notKeys := make([]string, len(s.Not.Value))
|
||||
notEntities := make(map[string]*Schema)
|
||||
z = 0
|
||||
for i := range s.Not.Value {
|
||||
g := s.Not.Value[i].Value
|
||||
if !g.IsSchemaReference() {
|
||||
k := g.Schema()
|
||||
r := low.GenerateHashString(k)
|
||||
notEntities[r] = k
|
||||
notKeys = append(notKeys, r)
|
||||
notKeys[z] = r
|
||||
z++
|
||||
}
|
||||
}
|
||||
sort.Strings(notKeys)
|
||||
@@ -305,15 +325,17 @@ func (s *Schema) Hash() [32]byte {
|
||||
}
|
||||
|
||||
if len(s.Items.Value) > 0 {
|
||||
itemsKeys := make([]string, 0, len(s.Items.Value))
|
||||
itemsKeys := make([]string, len(s.Items.Value))
|
||||
itemsEntities := make(map[string]*Schema)
|
||||
z = 0
|
||||
for i := range s.Items.Value {
|
||||
g := s.Items.Value[i].Value
|
||||
if !g.IsSchemaReference() {
|
||||
k := g.Schema()
|
||||
r := low.GenerateHashString(k)
|
||||
itemsEntities[r] = k
|
||||
itemsKeys = append(itemsKeys, r)
|
||||
itemsKeys[z] = r
|
||||
z++
|
||||
}
|
||||
}
|
||||
sort.Strings(itemsKeys)
|
||||
@@ -322,9 +344,14 @@ func (s *Schema) Hash() [32]byte {
|
||||
}
|
||||
}
|
||||
// add extensions to hash
|
||||
keys = make([]string, len(s.Extensions))
|
||||
z = 0
|
||||
for k := range s.Extensions {
|
||||
d = append(d, fmt.Sprintf("%v-%x", k.Value, s.Extensions[k].Value))
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(s.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
d = append(d, keys...)
|
||||
if s.Example.Value != nil {
|
||||
d = append(d, low.GenerateHashString(s.Example.Value))
|
||||
}
|
||||
|
||||
@@ -263,6 +263,28 @@ func TestSchema_Hash(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkSchema_Hash(b *testing.B) {
|
||||
|
||||
//create two versions
|
||||
testSpec := test_get_schema_blob()
|
||||
var sc1n yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(testSpec), &sc1n)
|
||||
sch1 := Schema{}
|
||||
_ = low.BuildModel(&sc1n, &sch1)
|
||||
_ = sch1.Build(sc1n.Content[0], nil)
|
||||
|
||||
var sc2n yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(testSpec), &sc2n)
|
||||
sch2 := Schema{}
|
||||
_ = low.BuildModel(&sc2n, &sch2)
|
||||
_ = sch2.Build(sc2n.Content[0], nil)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
assert.Equal(b, sch1.Hash(), sch2.Hash())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_Schema_31(t *testing.T) {
|
||||
testSpec := `$schema: https://something
|
||||
type:
|
||||
@@ -1246,26 +1268,86 @@ func TestExtractSchema_OneOfRef(t *testing.T) {
|
||||
func TestSchema_Hash_Equal(t *testing.T) {
|
||||
|
||||
left := `schema:
|
||||
$schema: https://athing.com
|
||||
multipleOf: 1
|
||||
maximum: 10
|
||||
minimum: 1
|
||||
maxLength: 10
|
||||
minLength: 1
|
||||
pattern: something
|
||||
format: another
|
||||
maxItems: 10
|
||||
minItems: 1
|
||||
uniqueItems: 1
|
||||
maxProperties: 10
|
||||
minProperties: 1
|
||||
additionalProperties: anything
|
||||
description: milky
|
||||
contentEncoding: rubber shoes
|
||||
contentMediaType: paper tiger
|
||||
default:
|
||||
type: jazz
|
||||
nullable: true
|
||||
readOnly: true
|
||||
writeOnly: true
|
||||
deprecated: true
|
||||
exclusiveMaximum: 23
|
||||
exclusiveMinimum: 10
|
||||
type:
|
||||
- int
|
||||
x-coffee: black
|
||||
enum:
|
||||
- one
|
||||
- two
|
||||
x-toast: burned
|
||||
title: an OK message
|
||||
required:
|
||||
- propA
|
||||
enum:
|
||||
- one
|
||||
properties:
|
||||
propA:
|
||||
title: a proxy property
|
||||
type: string`
|
||||
|
||||
right := `schema:
|
||||
$schema: https://athing.com
|
||||
multipleOf: 1
|
||||
maximum: 10
|
||||
x-coffee: black
|
||||
minimum: 1
|
||||
maxLength: 10
|
||||
minLength: 1
|
||||
pattern: something
|
||||
format: another
|
||||
maxItems: 10
|
||||
minItems: 1
|
||||
uniqueItems: 1
|
||||
maxProperties: 10
|
||||
minProperties: 1
|
||||
additionalProperties: anything
|
||||
description: milky
|
||||
contentEncoding: rubber shoes
|
||||
contentMediaType: paper tiger
|
||||
default:
|
||||
type: jazz
|
||||
nullable: true
|
||||
readOnly: true
|
||||
writeOnly: true
|
||||
deprecated: true
|
||||
exclusiveMaximum: 23
|
||||
exclusiveMinimum: 10
|
||||
type:
|
||||
- int
|
||||
enum:
|
||||
- one
|
||||
- two
|
||||
x-toast: burned
|
||||
title: an OK message
|
||||
required:
|
||||
- propA
|
||||
properties:
|
||||
propA:
|
||||
title: a proxy property
|
||||
type: string
|
||||
required:
|
||||
- propA`
|
||||
type: string`
|
||||
|
||||
var lNode, rNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tag represents a low-level Tag instance that is backed by a low-level one.
|
||||
@@ -42,6 +46,29 @@ func (t *Tag) GetExtensions() map[low.KeyReference[string]]low.ValueReference[an
|
||||
return t.Extensions
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the Info object
|
||||
func (t *Tag) Hash() [32]byte {
|
||||
var f []string
|
||||
if !t.Name.IsEmpty() {
|
||||
f = append(f, t.Name.Value)
|
||||
}
|
||||
if !t.Description.IsEmpty() {
|
||||
f = append(f, t.Description.Value)
|
||||
}
|
||||
if !t.ExternalDocs.IsEmpty() {
|
||||
f = append(f, low.GenerateHashString(t.ExternalDocs.Value))
|
||||
}
|
||||
keys := make([]string, len(t.Extensions))
|
||||
z := 0
|
||||
for k := range t.Extensions {
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(t.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
// TODO: future mutation API experiment code is here. this snippet is to re-marshal the object.
|
||||
//func (t *Tag) MarshalYAML() (interface{}, error) {
|
||||
// m := make(map[string]interface{})
|
||||
|
||||
@@ -55,3 +55,33 @@ externalDocs:
|
||||
err = n.Build(idxNode.Content[0], idx)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTag_Hash(t *testing.T) {
|
||||
|
||||
left := `name: melody
|
||||
description: my princess
|
||||
externalDocs:
|
||||
url: https://pb33f.io
|
||||
x-b33f: princess`
|
||||
|
||||
right := `name: melody
|
||||
description: my princess
|
||||
externalDocs:
|
||||
url: https://pb33f.io
|
||||
x-b33f: princess`
|
||||
|
||||
var lNode, rNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||
|
||||
// create low level objects
|
||||
var lDoc Tag
|
||||
var rDoc Tag
|
||||
_ = low.BuildModel(lNode.Content[0], &lDoc)
|
||||
_ = low.BuildModel(rNode.Content[0], &rDoc)
|
||||
_ = lDoc.Build(lNode.Content[0], nil)
|
||||
_ = rDoc.Build(rNode.Content[0], nil)
|
||||
|
||||
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -38,17 +39,29 @@ func (x *XML) GetExtensions() map[low.KeyReference[string]]low.ValueReference[an
|
||||
|
||||
// Hash generates a SHA256 hash of the XML object using properties
|
||||
func (x *XML) Hash() [32]byte {
|
||||
// calculate a hash from every property.
|
||||
d := []string{
|
||||
x.Name.Value,
|
||||
x.Namespace.Value,
|
||||
x.Prefix.Value,
|
||||
fmt.Sprint(x.Attribute.Value),
|
||||
fmt.Sprint(x.Wrapped.Value),
|
||||
var f []string
|
||||
if !x.Name.IsEmpty() {
|
||||
f = append(f, x.Name.Value)
|
||||
}
|
||||
// add extensions to hash
|
||||
if !x.Namespace.IsEmpty() {
|
||||
f = append(f, x.Namespace.Value)
|
||||
}
|
||||
if !x.Prefix.IsEmpty() {
|
||||
f = append(f, x.Prefix.Value)
|
||||
}
|
||||
if !x.Attribute.IsEmpty() {
|
||||
f = append(f, fmt.Sprint(x.Attribute.Value))
|
||||
}
|
||||
if !x.Wrapped.IsEmpty() {
|
||||
f = append(f, fmt.Sprint(x.Wrapped.Value))
|
||||
}
|
||||
keys := make([]string, len(x.Extensions))
|
||||
z := 0
|
||||
for k := range x.Extensions {
|
||||
d = append(d, fmt.Sprintf("%v-%x", k.Value, x.Extensions[k].Value))
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(x.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
return sha256.Sum256([]byte(strings.Join(d, "|")))
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type SwaggerParameter interface {
|
||||
GetMaxItems() *NodeReference[int]
|
||||
GetMinItems() *NodeReference[int]
|
||||
GetUniqueItems() *NodeReference[bool]
|
||||
GetEnum() *NodeReference[[]ValueReference[string]]
|
||||
GetEnum() *NodeReference[[]ValueReference[any]]
|
||||
GetMultipleOf() *NodeReference[int]
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ type SwaggerHeader interface {
|
||||
GetMaxItems() *NodeReference[int]
|
||||
GetMinItems() *NodeReference[int]
|
||||
GetUniqueItems() *NodeReference[bool]
|
||||
GetEnum() *NodeReference[[]ValueReference[string]]
|
||||
GetEnum() *NodeReference[[]ValueReference[any]]
|
||||
GetMultipleOf() *NodeReference[int]
|
||||
GetItems() *NodeReference[any] // requires cast.
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -34,7 +35,7 @@ type Header struct {
|
||||
MaxItems low.NodeReference[int]
|
||||
MinItems low.NodeReference[int]
|
||||
UniqueItems low.NodeReference[bool]
|
||||
Enum low.NodeReference[[]low.ValueReference[string]]
|
||||
Enum low.NodeReference[[]low.ValueReference[any]]
|
||||
MultipleOf low.NodeReference[int]
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
@@ -120,16 +121,26 @@ func (h *Header) Hash() [32]byte {
|
||||
if h.Pattern.Value != "" {
|
||||
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(h.Pattern.Value)))))
|
||||
}
|
||||
if len(h.Enum.Value) > 0 {
|
||||
for k := range h.Enum.Value {
|
||||
f = append(f, fmt.Sprint(h.Enum.Value[k].Value))
|
||||
}
|
||||
}
|
||||
|
||||
keys := make([]string, len(h.Extensions))
|
||||
z := 0
|
||||
for k := range h.Extensions {
|
||||
f = append(f, fmt.Sprintf("%s-%v", k.Value, h.Extensions[k].Value))
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(h.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
|
||||
keys = make([]string, len(h.Enum.Value))
|
||||
z = 0
|
||||
for k := range h.Enum.Value {
|
||||
keys[z] = fmt.Sprint(h.Enum.Value[k].Value)
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
if h.Items.Value != nil {
|
||||
f = append(f, fmt.Sprintf("%x", h.Items.Value.Hash()))
|
||||
f = append(f, low.GenerateHashString(h.Items.Value))
|
||||
}
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
@@ -189,7 +200,7 @@ func (h *Header) GetMinItems() *low.NodeReference[int] {
|
||||
func (h *Header) GetUniqueItems() *low.NodeReference[bool] {
|
||||
return &h.UniqueItems
|
||||
}
|
||||
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[string]] {
|
||||
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
|
||||
return &h.Enum
|
||||
}
|
||||
func (h *Header) GetMultipleOf() *low.NodeReference[int] {
|
||||
|
||||
@@ -34,7 +34,7 @@ type Items struct {
|
||||
MaxItems low.NodeReference[int]
|
||||
MinItems low.NodeReference[int]
|
||||
UniqueItems low.NodeReference[bool]
|
||||
Enum low.NodeReference[[]low.ValueReference[string]]
|
||||
Enum low.NodeReference[[]low.ValueReference[any]]
|
||||
MultipleOf low.NodeReference[int]
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
@@ -186,7 +186,7 @@ func (i *Items) GetMinItems() *low.NodeReference[int] {
|
||||
func (i *Items) GetUniqueItems() *low.NodeReference[bool] {
|
||||
return &i.UniqueItems
|
||||
}
|
||||
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[string]] {
|
||||
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
|
||||
return &i.Enum
|
||||
}
|
||||
func (i *Items) GetMultipleOf() *low.NodeReference[int] {
|
||||
|
||||
@@ -146,10 +146,14 @@ func (o *Operation) Hash() [32]byte {
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
keys = make([]string, len(o.Extensions))
|
||||
z := 0
|
||||
for k := range o.Extensions {
|
||||
f = append(f, fmt.Sprintf("%s-%x", k.Value,
|
||||
sha256.Sum256([]byte(fmt.Sprint(o.Extensions[k].Value)))))
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(o.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ type Parameter struct {
|
||||
MaxItems low.NodeReference[int]
|
||||
MinItems low.NodeReference[int]
|
||||
UniqueItems low.NodeReference[bool]
|
||||
Enum low.NodeReference[[]low.ValueReference[string]]
|
||||
Enum low.NodeReference[[]low.ValueReference[any]]
|
||||
MultipleOf low.NodeReference[int]
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func (p *Parameter) GetMinItems() *low.NodeReference[int] {
|
||||
func (p *Parameter) GetUniqueItems() *low.NodeReference[bool] {
|
||||
return &p.UniqueItems
|
||||
}
|
||||
func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[string]] {
|
||||
func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
|
||||
return &p.Enum
|
||||
}
|
||||
func (p *Parameter) GetMultipleOf() *low.NodeReference[int] {
|
||||
|
||||
82
datamodel/low/v3/path_item_test.go
Normal file
82
datamodel/low/v3/path_item_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v3
|
||||
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPathItem_Hash(t *testing.T) {
|
||||
|
||||
yml := `description: a path item
|
||||
summary: it's another path item
|
||||
servers:
|
||||
- url: https://pb33f.io
|
||||
parameters:
|
||||
- in: head
|
||||
get:
|
||||
description: get me
|
||||
post:
|
||||
description: post me
|
||||
put:
|
||||
description: put me
|
||||
patch:
|
||||
description: patch me
|
||||
delete:
|
||||
description: delete me
|
||||
head:
|
||||
description: top
|
||||
options:
|
||||
description: choices
|
||||
trace:
|
||||
description: find me
|
||||
x-byebye: boebert`
|
||||
|
||||
var idxNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||
idx := index.NewSpecIndex(&idxNode)
|
||||
|
||||
var n PathItem
|
||||
_ = low.BuildModel(idxNode.Content[0], &n)
|
||||
_ = n.Build(idxNode.Content[0], idx)
|
||||
|
||||
yml2 := `get:
|
||||
description: get me
|
||||
post:
|
||||
description: post me
|
||||
servers:
|
||||
- url: https://pb33f.io
|
||||
parameters:
|
||||
- in: head
|
||||
put:
|
||||
description: put me
|
||||
patch:
|
||||
description: patch me
|
||||
delete:
|
||||
description: delete me
|
||||
head:
|
||||
description: top
|
||||
options:
|
||||
description: choices
|
||||
trace:
|
||||
description: find me
|
||||
x-byebye: boebert
|
||||
description: a path item
|
||||
summary: it's another path item`
|
||||
|
||||
var idxNode2 yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml2), &idxNode2)
|
||||
idx2 := index.NewSpecIndex(&idxNode2)
|
||||
|
||||
var n2 PathItem
|
||||
_ = low.BuildModel(idxNode2.Content[0], &n2)
|
||||
_ = n2.Build(idxNode2.Content[0], idx2)
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
}
|
||||
@@ -428,3 +428,49 @@ func TestPaths_Build_BrokenOp(t *testing.T) {
|
||||
err = n.Build(idxNode.Content[0], idx)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPaths_Hash(t *testing.T) {
|
||||
|
||||
yml := `/french/toast:
|
||||
description: toast
|
||||
/french/hen:
|
||||
description: chicken
|
||||
/french/food:
|
||||
description: the worst.
|
||||
x-france: french`
|
||||
|
||||
var idxNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||
idx := index.NewSpecIndex(&idxNode)
|
||||
|
||||
var n Paths
|
||||
_ = low.BuildModel(idxNode.Content[0], &n)
|
||||
_ = n.Build(idxNode.Content[0], idx)
|
||||
|
||||
yml2 := `/french/toast:
|
||||
description: toast
|
||||
/french/hen:
|
||||
description: chicken
|
||||
/french/food:
|
||||
description: the worst.
|
||||
x-france: french`
|
||||
|
||||
var idxNode2 yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml2), &idxNode2)
|
||||
idx2 := index.NewSpecIndex(&idxNode2)
|
||||
|
||||
var n2 Paths
|
||||
_ = low.BuildModel(idxNode2.Content[0], &n2)
|
||||
_ = n2.Build(idxNode2.Content[0], idx2)
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
a, b := n.FindPathAndKey("/french/toast")
|
||||
assert.NotNil(t, a)
|
||||
assert.NotNil(t, b)
|
||||
|
||||
a, b = n.FindPathAndKey("I do not exist")
|
||||
assert.Nil(t, a)
|
||||
assert.Nil(t, b)
|
||||
|
||||
}
|
||||
|
||||
@@ -53,3 +53,49 @@ func TestRequestBody_Fail(t *testing.T) {
|
||||
err = n.Build(idxNode.Content[0], idx)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRequestBody_Hash(t *testing.T) {
|
||||
|
||||
yml := `description: nice toast
|
||||
content:
|
||||
jammy/toast:
|
||||
schema:
|
||||
type: int
|
||||
honey/toast:
|
||||
schema:
|
||||
type: int
|
||||
required: true
|
||||
x-toast: nice
|
||||
`
|
||||
|
||||
var idxNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||
idx := index.NewSpecIndex(&idxNode)
|
||||
|
||||
var n RequestBody
|
||||
_ = low.BuildModel(idxNode.Content[0], &n)
|
||||
_ = n.Build(idxNode.Content[0], idx)
|
||||
|
||||
yml2 := `description: nice toast
|
||||
content:
|
||||
jammy/toast:
|
||||
schema:
|
||||
type: int
|
||||
honey/toast:
|
||||
schema:
|
||||
type: int
|
||||
required: true
|
||||
x-toast: nice`
|
||||
|
||||
var idxNode2 yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml2), &idxNode2)
|
||||
idx2 := index.NewSpecIndex(&idxNode2)
|
||||
|
||||
var n2 RequestBody
|
||||
_ = low.BuildModel(idxNode2.Content[0], &n2)
|
||||
_ = n2.Build(idxNode2.Content[0], idx2)
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -95,18 +96,38 @@ func (r *Response) Hash() [32]byte {
|
||||
if r.Description.Value != "" {
|
||||
f = append(f, r.Description.Value)
|
||||
}
|
||||
|
||||
keys := make([]string, len(r.Headers.Value))
|
||||
z := 0
|
||||
for k := range r.Headers.Value {
|
||||
f = append(f, low.GenerateHashString(r.Headers.Value[k].Value))
|
||||
keys[z] = low.GenerateHashString(r.Headers.Value[k].Value)
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
keys = make([]string, len(r.Content.Value))
|
||||
z = 0
|
||||
for k := range r.Content.Value {
|
||||
f = append(f, low.GenerateHashString(r.Content.Value[k].Value))
|
||||
keys[z] = low.GenerateHashString(r.Content.Value[k].Value)
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
keys = make([]string, len(r.Links.Value))
|
||||
z = 0
|
||||
for k := range r.Links.Value {
|
||||
f = append(f, low.GenerateHashString(r.Links.Value[k].Value))
|
||||
keys[z] = low.GenerateHashString(r.Links.Value[k].Value)
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
keys = make([]string, len(r.Extensions))
|
||||
z = 0
|
||||
for k := range r.Extensions {
|
||||
f = append(f, fmt.Sprintf("%s-%x", k.Value,
|
||||
sha256.Sum256([]byte(fmt.Sprint(r.Extensions[k].Value)))))
|
||||
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(r.Extensions[k].Value))))
|
||||
z++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
f = append(f, keys...)
|
||||
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
||||
}
|
||||
|
||||
@@ -173,3 +173,64 @@ func TestResponses_Build_FailBadLinks(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestResponse_Hash(t *testing.T) {
|
||||
|
||||
yml := `description: nice toast
|
||||
headers:
|
||||
heady:
|
||||
description: a header
|
||||
handy:
|
||||
description: a handy
|
||||
content:
|
||||
nice/toast:
|
||||
schema:
|
||||
type: int
|
||||
nice/roast:
|
||||
schema:
|
||||
type: int
|
||||
x-jam: toast
|
||||
x-ham: jam
|
||||
links:
|
||||
linky:
|
||||
operationId: one two toast`
|
||||
|
||||
var idxNode yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||
idx := index.NewSpecIndex(&idxNode)
|
||||
|
||||
var n Response
|
||||
_ = low.BuildModel(idxNode.Content[0], &n)
|
||||
_ = n.Build(idxNode.Content[0], idx)
|
||||
|
||||
yml2 := `description: nice toast
|
||||
x-ham: jam
|
||||
headers:
|
||||
heady:
|
||||
description: a header
|
||||
handy:
|
||||
description: a handy
|
||||
content:
|
||||
nice/toast:
|
||||
schema:
|
||||
type: int
|
||||
nice/roast:
|
||||
schema:
|
||||
type: int
|
||||
x-jam: toast
|
||||
links:
|
||||
linky:
|
||||
operationId: one two toast`
|
||||
|
||||
var idxNode2 yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml2), &idxNode2)
|
||||
idx2 := index.NewSpecIndex(&idxNode2)
|
||||
|
||||
var n2 Response
|
||||
_ = low.BuildModel(idxNode2.Content[0], &n2)
|
||||
_ = n2.Build(idxNode2.Content[0], idx2)
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"gopkg.in/yaml.v3"
|
||||
"strings"
|
||||
@@ -279,3 +280,40 @@ func ExtractStringValueSliceChanges(lParam, rParam []low.ValueReference[string],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractRawValueSliceChanges will compare two low level interface{} slices for changes.
|
||||
func ExtractRawValueSliceChanges(lParam, rParam []low.ValueReference[any],
|
||||
changes *[]*Change, label string, breaking bool) {
|
||||
lKeys := make([]string, len(lParam))
|
||||
rKeys := make([]string, len(rParam))
|
||||
lValues := make(map[string]low.ValueReference[any])
|
||||
rValues := make(map[string]low.ValueReference[any])
|
||||
for i := range lParam {
|
||||
lKeys[i] = strings.ToLower(fmt.Sprint(lParam[i].Value))
|
||||
lValues[lKeys[i]] = lParam[i]
|
||||
}
|
||||
for i := range rParam {
|
||||
rKeys[i] = strings.ToLower(fmt.Sprint(rParam[i].Value))
|
||||
rValues[rKeys[i]] = rParam[i]
|
||||
}
|
||||
for i := range lValues {
|
||||
if _, ok := rValues[i]; !ok {
|
||||
CreateChange(changes, PropertyRemoved, label,
|
||||
lValues[i].ValueNode,
|
||||
nil,
|
||||
breaking,
|
||||
lValues[i].Value,
|
||||
nil)
|
||||
}
|
||||
}
|
||||
for i := range rValues {
|
||||
if _, ok := lValues[i]; !ok {
|
||||
CreateChange(changes, PropertyAdded, label,
|
||||
nil,
|
||||
rValues[i].ValueNode,
|
||||
false,
|
||||
nil,
|
||||
rValues[i].Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func CompareHeaders(l, r any) *HeaderChanges {
|
||||
|
||||
// enum
|
||||
if len(lHeader.Enum.Value) > 0 || len(rHeader.Enum.Value) > 0 {
|
||||
ExtractStringValueSliceChanges(lHeader.Enum.Value, rHeader.Enum.Value, &changes, v3.EnumLabel, true)
|
||||
ExtractRawValueSliceChanges(lHeader.Enum.Value, rHeader.Enum.Value, &changes, v3.EnumLabel, true)
|
||||
}
|
||||
|
||||
// items
|
||||
|
||||
@@ -242,7 +242,7 @@ func CompareParameters(l, r any) *ParameterChanges {
|
||||
|
||||
// enum
|
||||
if len(lParam.Enum.Value) > 0 || len(rParam.Enum.Value) > 0 {
|
||||
ExtractStringValueSliceChanges(lParam.Enum.Value, rParam.Enum.Value, &changes, v3.EnumLabel, true)
|
||||
ExtractRawValueSliceChanges(lParam.Enum.Value, rParam.Enum.Value, &changes, v3.EnumLabel, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user