diff --git a/datamodel/high/v3/link.go b/datamodel/high/v3/link.go index d9f62b5..c44aa63 100644 --- a/datamodel/high/v3/link.go +++ b/datamodel/high/v3/link.go @@ -38,7 +38,7 @@ func NewLink(link *low.Link) *Link { l.OperationRef = link.OperationRef.Value l.OperationId = link.OperationId.Value params := make(map[string]string) - for k, v := range link.Parameters.Value { + for k, v := range link.Parameters { params[k.Value] = v.Value } l.Parameters = params diff --git a/datamodel/low/v3/callback.go b/datamodel/low/v3/callback.go index 1b76ea6..9e9cd41 100644 --- a/datamodel/low/v3/callback.go +++ b/datamodel/low/v3/callback.go @@ -4,9 +4,12 @@ package v3 import ( + "crypto/sha256" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" + "sort" + "strings" ) // Callback represents a low-level Callback object for OpenAPI 3+. @@ -59,3 +62,18 @@ func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error { } return nil } + +// Hash will return a consistent SHA256 Hash of the Callback object +func (cb *Callback) Hash() [32]byte { + var f []string + var keys []string + keys = make([]string, len(cb.Expression.Value)) + z := 0 + for k := range cb.Expression.Value { + keys[z] = low.GenerateHashString(cb.Expression.Value[k].Value) + z++ + } + sort.Strings(keys) + f = append(f, keys...) + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} diff --git a/datamodel/low/v3/callback_test.go b/datamodel/low/v3/callback_test.go index 15eaf2c..cba415a 100644 --- a/datamodel/low/v3/callback_test.go +++ b/datamodel/low/v3/callback_test.go @@ -111,3 +111,44 @@ func TestCallback_Build_Using_InlineRef(t *testing.T) { assert.Equal(t, "this is something", exp.Value.Post.Value.RequestBody.Value.Description.Value) } + +func TestCallback_Hash(t *testing.T) { + + yml := `x-seed: grow +pizza: + description: cheesy +burgers: + description: tasty! +beer: + description: fantastic +x-weed: loved` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + idx := index.NewSpecIndex(&idxNode) + + var n Callback + _ = low.BuildModel(idxNode.Content[0], &n) + _ = n.Build(idxNode.Content[0], idx) + + yml2 := `burgers: + description: tasty! +pizza: + description: cheesy +x-weed: loved +x-seed: grow +beer: + description: fantastic +` + var idxNode2 yaml.Node + _ = yaml.Unmarshal([]byte(yml2), &idxNode2) + idx2 := index.NewSpecIndex(&idxNode2) + + var n2 Callback + _ = low.BuildModel(idxNode2.Content[0], &n2) + _ = n2.Build(idxNode2.Content[0], idx2) + + // hash + assert.Equal(t, n.Hash(), n2.Hash()) + +} diff --git a/datamodel/low/v3/media_type_test.go b/datamodel/low/v3/media_type_test.go index 5d157c2..c75b098 100644 --- a/datamodel/low/v3/media_type_test.go +++ b/datamodel/low/v3/media_type_test.go @@ -141,5 +141,4 @@ example: a thing` // hash assert.Equal(t, n.Hash(), n2.Hash()) - } diff --git a/datamodel/low/v3/operation.go b/datamodel/low/v3/operation.go index 8defb2f..d4edc96 100644 --- a/datamodel/low/v3/operation.go +++ b/datamodel/low/v3/operation.go @@ -182,16 +182,22 @@ func (o *Operation) Hash() [32]byte { for k := range o.Parameters.Value { keys[k] = low.GenerateHashString(o.Parameters.Value[k].Value) } - sort.Strings(keys) f = append(f, keys...) + + keys = make([]string, len(o.Callbacks.Value)) + z := 0 + for k := range o.Callbacks.Value { + keys[z] = low.GenerateHashString(o.Callbacks.Value[k].Value) + z++ + } + sort.Strings(keys) + f = append(f, keys...) + for k := range o.Extensions { f = append(f, fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(o.Extensions[k].Value))))) } - - // TODO: add callbacks in here. - return sha256.Sum256([]byte(strings.Join(f, "|"))) } @@ -200,15 +206,12 @@ func (o *Operation) Hash() [32]byte { func (o *Operation) GetTags() low.NodeReference[[]low.ValueReference[string]] { return o.Tags } - func (o *Operation) GetSummary() low.NodeReference[string] { return o.Summary } - func (o *Operation) GetDescription() low.NodeReference[string] { return o.Description } - func (o *Operation) GetExternalDocs() low.NodeReference[any] { return low.NodeReference[any]{ ValueNode: o.ExternalDocs.ValueNode, @@ -216,19 +219,15 @@ func (o *Operation) GetExternalDocs() low.NodeReference[any] { Value: o.ExternalDocs.Value, } } - func (o *Operation) GetOperationId() low.NodeReference[string] { return o.OperationId } - func (o *Operation) GetDeprecated() low.NodeReference[bool] { return o.Deprecated } - func (o *Operation) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { return o.Extensions } - func (o *Operation) GetResponses() low.NodeReference[any] { return low.NodeReference[any]{ ValueNode: o.Responses.ValueNode, @@ -236,7 +235,6 @@ func (o *Operation) GetResponses() low.NodeReference[any] { Value: o.Responses.Value, } } - func (o *Operation) GetParameters() low.NodeReference[any] { return low.NodeReference[any]{ ValueNode: o.Parameters.ValueNode, @@ -244,7 +242,6 @@ func (o *Operation) GetParameters() low.NodeReference[any] { Value: o.Parameters.Value, } } - func (o *Operation) GetSecurity() low.NodeReference[any] { return low.NodeReference[any]{ ValueNode: o.Security.ValueNode, @@ -252,7 +249,6 @@ func (o *Operation) GetSecurity() low.NodeReference[any] { Value: o.Security.Value, } } - func (o *Operation) GetServers() low.NodeReference[any] { return low.NodeReference[any]{ ValueNode: o.Servers.ValueNode, @@ -260,7 +256,6 @@ func (o *Operation) GetServers() low.NodeReference[any] { Value: o.Servers.Value, } } - -//func (o *Operation) GetCallbacks() low.NodeReference[[]low.ValueReference[any]] { -// return o.Produces -//} +func (o *Operation) GetCallbacks() low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]] { + return o.Callbacks +} diff --git a/datamodel/low/v3/operation_test.go b/datamodel/low/v3/operation_test.go index 8ae4ca0..4cc27e7 100644 --- a/datamodel/low/v3/operation_test.go +++ b/datamodel/low/v3/operation_test.go @@ -5,6 +5,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" @@ -191,3 +192,97 @@ func TestOperation_Build_FailServers(t *testing.T) { err = n.Build(idxNode.Content[0], idx) assert.Error(t, err) } + +func TestOperation_Hash_n_Grab(t *testing.T) { + + yml := `tags: + - nice + - rice +summary: a thing +description: another thing +externalDocs: + url: https://pb33f.io/docs +operationId: sleepyMornings +parameters: + - name: parammy + in: my head +requestBody: + description: a thing +responses: + "200": + description: ok +callbacks: + callMe: + something: blue +deprecated: true +security: + - lego: + dont: stand + or: eat +servers: + - url: https://pb33f.io +x-mint: sweet` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + idx := index.NewSpecIndex(&idxNode) + + var n Operation + _ = low.BuildModel(idxNode.Content[0], &n) + _ = n.Build(idxNode.Content[0], idx) + + yml2 := `tags: + - nice + - rice +summary: a thing +description: another thing +externalDocs: + url: https://pb33f.io/docs +operationId: sleepyMornings +parameters: + - name: parammy + in: my head +requestBody: + description: a thing +responses: + "200": + description: ok +callbacks: + callMe: + something: blue +deprecated: true +security: + - lego: + dont: stand + or: eat +servers: + - url: https://pb33f.io +x-mint: sweet` + + var idxNode2 yaml.Node + _ = yaml.Unmarshal([]byte(yml2), &idxNode2) + idx2 := index.NewSpecIndex(&idxNode2) + + var n2 Operation + _ = low.BuildModel(idxNode2.Content[0], &n2) + _ = n2.Build(idxNode2.Content[0], idx2) + + // hash + assert.Equal(t, n.Hash(), n2.Hash()) + + // n grab + assert.Len(t, n.GetTags().Value, 2) + assert.Equal(t, "a thing", n.GetSummary().Value) + assert.Equal(t, "another thing", n.GetDescription().Value) + assert.Equal(t, "https://pb33f.io/docs", n.GetExternalDocs().Value.(*base.ExternalDoc).URL.Value) + assert.Equal(t, "sleepyMornings", n.GetOperationId().Value) + assert.Len(t, n.GetParameters().Value, 1) + assert.Len(t, n.GetSecurity().Value, 1) + assert.True(t, n.GetDeprecated().Value) + assert.Len(t, n.GetExtensions(), 1) + assert.Len(t, n.GetServers().Value.([]low.ValueReference[*Server]), 1) + assert.Len(t, n.GetCallbacks().Value, 1) + assert.Len(t, n.GetResponses().Value.(*Responses).Codes, 1) + assert.Nil(t, n.FindSecurityRequirement("I do not exist")) + +} diff --git a/what-changed/model/link.go b/what-changed/model/link.go index 51a8312..d92d5f5 100644 --- a/what-changed/model/link.go +++ b/what-changed/model/link.go @@ -109,11 +109,11 @@ func CompareLinks(l, r *v3.Link) *LinkChanges { // parameters lValues := make(map[string]low.ValueReference[string]) rValues := make(map[string]low.ValueReference[string]) - for i := range l.Parameters.Value { - lValues[i.Value] = l.Parameters.Value[i] + for i := range l.Parameters { + lValues[i.Value] = l.Parameters[i] } - for i := range r.Parameters.Value { - rValues[i.Value] = r.Parameters.Value[i] + for i := range r.Parameters { + rValues[i.Value] = r.Parameters[i] } for k := range lValues { if _, ok := rValues[k]; !ok {