Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions bundler/bundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"sync"
"testing"

"github.com/pb33f/libopenapi/datamodel/low"

"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high/base"
Expand Down Expand Up @@ -575,8 +573,8 @@ func TestBundleDocument_BundleBytesComposed_NestedFiles(t *testing.T) {
assert.Equal(t, len1, len2)

// hash the two files and ensure they match
hash1 := low.HashToString(sha256.Sum256(preBundled))
hash2 := low.HashToString(sha256.Sum256(bundledBytes))
hash1 := sha256.Sum256(preBundled)
hash2 := sha256.Sum256(bundledBytes)
assert.Equal(t, hash1, hash2)
}
}
Expand Down
11 changes: 9 additions & 2 deletions datamodel/high/overlay/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (
)

// Action represents a high-level Overlay Action Object.
// https://spec.openapis.org/overlay/v1.0.0#action-object
// https://spec.openapis.org/overlay/v1.1.0#action-object
type Action struct {
Target string `json:"target,omitempty" yaml:"target,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Update *yaml.Node `json:"update,omitempty" yaml:"update,omitempty"`
Remove bool `json:"remove,omitempty" yaml:"remove,omitempty"`
Copy string `json:"copy,omitempty" yaml:"copy,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Action
}
Expand All @@ -37,6 +38,9 @@ func NewAction(action *low.Action) *Action {
if !action.Remove.IsEmpty() {
a.Remove = action.Remove.Value
}
if !action.Copy.IsEmpty() {
a.Copy = action.Copy.Value
}
a.Extensions = high.ExtractExtensions(action.Extensions)
return a
}
Expand All @@ -57,14 +61,17 @@ func (a *Action) Render() ([]byte, error) {
}

// MarshalYAML creates a ready to render YAML representation of the Action object.
func (a *Action) MarshalYAML() (interface{}, error) {
func (a *Action) MarshalYAML() (any, error) {
m := orderedmap.New[string, any]()
if a.Target != "" {
m.Set("target", a.Target)
}
if a.Description != "" {
m.Set("description", a.Description)
}
if a.Copy != "" {
m.Set("copy", a.Copy)
}
if a.Update != nil {
m.Set("update", a.Update)
}
Expand Down
124 changes: 124 additions & 0 deletions datamodel/high/overlay/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,127 @@ func TestAction_MarshalYAML_Empty(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, result)
}

func TestNewAction_WithCopy(t *testing.T) {
yml := `target: $.paths./users.post.responses.201
copy: $.paths./users.get.responses.200`

var node yaml.Node
err := yaml.Unmarshal([]byte(yml), &node)
require.NoError(t, err)

var lowAction lowoverlay.Action
err = low.BuildModel(node.Content[0], &lowAction)
require.NoError(t, err)
err = lowAction.Build(context.Background(), nil, node.Content[0], nil)
require.NoError(t, err)

highAction := NewAction(&lowAction)

assert.Equal(t, "$.paths./users.post.responses.201", highAction.Target)
assert.Equal(t, "$.paths./users.get.responses.200", highAction.Copy)
assert.Nil(t, highAction.Update)
assert.False(t, highAction.Remove)
}

func TestNewAction_WithCopyAndUpdate(t *testing.T) {
yml := `target: $.paths./users.post
copy: $.paths./users.get
update:
summary: Overridden`

var node yaml.Node
err := yaml.Unmarshal([]byte(yml), &node)
require.NoError(t, err)

var lowAction lowoverlay.Action
err = low.BuildModel(node.Content[0], &lowAction)
require.NoError(t, err)
err = lowAction.Build(context.Background(), nil, node.Content[0], nil)
require.NoError(t, err)

highAction := NewAction(&lowAction)

assert.Equal(t, "$.paths./users.post", highAction.Target)
assert.Equal(t, "$.paths./users.get", highAction.Copy)
assert.NotNil(t, highAction.Update)
}

func TestNewAction_NoCopy(t *testing.T) {
yml := `target: $.info
update:
title: New`

var node yaml.Node
err := yaml.Unmarshal([]byte(yml), &node)
require.NoError(t, err)

var lowAction lowoverlay.Action
err = low.BuildModel(node.Content[0], &lowAction)
require.NoError(t, err)
err = lowAction.Build(context.Background(), nil, node.Content[0], nil)
require.NoError(t, err)

highAction := NewAction(&lowAction)

assert.Empty(t, highAction.Copy)
}

func TestAction_MarshalYAML_WithCopy(t *testing.T) {
yml := `target: $.info
copy: $.source
update:
title: Test`

var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)

var lowAction lowoverlay.Action
_ = low.BuildModel(node.Content[0], &lowAction)
_ = lowAction.Build(context.Background(), nil, node.Content[0], nil)

highAction := NewAction(&lowAction)

result, err := highAction.MarshalYAML()
require.NoError(t, err)
assert.NotNil(t, result)
}

func TestAction_MarshalYAML_OmitsEmptyCopy(t *testing.T) {
yml := `target: $.info
update:
title: New`

var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)

var lowAction lowoverlay.Action
_ = low.BuildModel(node.Content[0], &lowAction)
_ = lowAction.Build(context.Background(), nil, node.Content[0], nil)

highAction := NewAction(&lowAction)

rendered, err := highAction.Render()
require.NoError(t, err)
// Should NOT contain copy key when empty
assert.NotContains(t, string(rendered), "copy:")
}

func TestAction_Render_WithCopy(t *testing.T) {
yml := `target: $.paths./new
copy: $.paths./old`

var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)

var lowAction lowoverlay.Action
_ = low.BuildModel(node.Content[0], &lowAction)
_ = lowAction.Build(context.Background(), nil, node.Content[0], nil)

highAction := NewAction(&lowAction)

rendered, err := highAction.Render()
require.NoError(t, err)
assert.Contains(t, string(rendered), "target: $.paths./new")
assert.Contains(t, string(rendered), "copy: $.paths./old")
}
19 changes: 13 additions & 6 deletions datamodel/high/overlay/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (
)

// Info represents a high-level Overlay Info Object.
// https://spec.openapis.org/overlay/v1.0.0#info-object
// https://spec.openapis.org/overlay/v1.1.0#info-object
type Info struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Info
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Info
}

// NewInfo creates a new high-level Info instance from a low-level one.
Expand All @@ -29,6 +30,9 @@ func NewInfo(info *low.Info) *Info {
if !info.Version.IsEmpty() {
i.Version = info.Version.Value
}
if !info.Description.IsEmpty() {
i.Description = info.Description.Value
}
i.Extensions = high.ExtractExtensions(info.Extensions)
return i
}
Expand All @@ -49,14 +53,17 @@ func (i *Info) Render() ([]byte, error) {
}

// MarshalYAML creates a ready to render YAML representation of the Info object.
func (i *Info) MarshalYAML() (interface{}, error) {
func (i *Info) MarshalYAML() (any, error) {
m := orderedmap.New[string, any]()
if i.Title != "" {
m.Set("title", i.Title)
}
if i.Version != "" {
m.Set("version", i.Version)
}
if i.Description != "" {
m.Set("description", i.Description)
}
for pair := i.Extensions.First(); pair != nil; pair = pair.Next() {
m.Set(pair.Key(), pair.Value())
}
Expand Down
100 changes: 100 additions & 0 deletions datamodel/high/overlay/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,103 @@ func TestInfo_MarshalYAML_Empty(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, result)
}

func TestNewInfo_WithDescription(t *testing.T) {
yml := `title: My Overlay
version: 1.0.0
description: This is a **markdown** description`

var node yaml.Node
err := yaml.Unmarshal([]byte(yml), &node)
require.NoError(t, err)

var lowInfo lowoverlay.Info
err = low.BuildModel(node.Content[0], &lowInfo)
require.NoError(t, err)
err = lowInfo.Build(context.Background(), nil, node.Content[0], nil)
require.NoError(t, err)

highInfo := NewInfo(&lowInfo)

assert.Equal(t, "My Overlay", highInfo.Title)
assert.Equal(t, "1.0.0", highInfo.Version)
assert.Equal(t, "This is a **markdown** description", highInfo.Description)
}

func TestNewInfo_EmptyDescription(t *testing.T) {
yml := `title: Overlay`

var node yaml.Node
err := yaml.Unmarshal([]byte(yml), &node)
require.NoError(t, err)

var lowInfo lowoverlay.Info
err = low.BuildModel(node.Content[0], &lowInfo)
require.NoError(t, err)
err = lowInfo.Build(context.Background(), nil, node.Content[0], nil)
require.NoError(t, err)

highInfo := NewInfo(&lowInfo)

assert.Equal(t, "Overlay", highInfo.Title)
assert.Empty(t, highInfo.Description)
}

func TestInfo_MarshalYAML_WithDescription(t *testing.T) {
yml := `title: Test
version: 2.0.0
description: A long description here`

var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)

var lowInfo lowoverlay.Info
_ = low.BuildModel(node.Content[0], &lowInfo)
_ = lowInfo.Build(context.Background(), nil, node.Content[0], nil)

highInfo := NewInfo(&lowInfo)

result, err := highInfo.MarshalYAML()
require.NoError(t, err)
assert.NotNil(t, result)
}

func TestInfo_MarshalYAML_OmitsEmptyDescription(t *testing.T) {
yml := `title: Test
version: 1.0.0`

var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)

var lowInfo lowoverlay.Info
_ = low.BuildModel(node.Content[0], &lowInfo)
_ = lowInfo.Build(context.Background(), nil, node.Content[0], nil)

highInfo := NewInfo(&lowInfo)

rendered, err := highInfo.Render()
require.NoError(t, err)
// Should NOT contain description key when empty
assert.NotContains(t, string(rendered), "description:")
}

func TestInfo_Render_WithDescription(t *testing.T) {
yml := `title: My Overlay
version: 1.0.0
description: This overlay adds documentation`

var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)

var lowInfo lowoverlay.Info
_ = low.BuildModel(node.Content[0], &lowInfo)
_ = lowInfo.Build(context.Background(), nil, node.Content[0], nil)

highInfo := NewInfo(&lowInfo)

rendered, err := highInfo.Render()
require.NoError(t, err)
assert.Contains(t, string(rendered), "title: My Overlay")
assert.Contains(t, string(rendered), "version: 1.0.0")
assert.Contains(t, string(rendered), "description: This overlay adds documentation")
}
41 changes: 19 additions & 22 deletions datamodel/low/base/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package base

import (
"context"
"crypto/sha256"
"hash/maphash"

"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
Expand Down Expand Up @@ -61,27 +61,24 @@ func (c *Contact) GetKeyNode() *yaml.Node {
return c.KeyNode
}

// Hash will return a consistent SHA256 Hash of the Contact object
func (c *Contact) Hash() [32]byte {
// Use string builder pool
sb := low.GetStringBuilder()
defer low.PutStringBuilder(sb)

if !c.Name.IsEmpty() {
sb.WriteString(c.Name.Value)
sb.WriteByte('|')
}
if !c.URL.IsEmpty() {
sb.WriteString(c.URL.Value)
sb.WriteByte('|')
}
if !c.Email.IsEmpty() {
sb.WriteString(c.Email.Value)
sb.WriteByte('|')
}

// Note: Extensions are not included in the hash for Contact
return sha256.Sum256([]byte(sb.String()))
// Hash will return a consistent hash of the Contact object
func (c *Contact) Hash() uint64 {
return low.WithHasher(func(h *maphash.Hash) uint64 {
if !c.Name.IsEmpty() {
h.WriteString(c.Name.Value)
h.WriteByte(low.HASH_PIPE)
}
if !c.URL.IsEmpty() {
h.WriteString(c.URL.Value)
h.WriteByte(low.HASH_PIPE)
}
if !c.Email.IsEmpty() {
h.WriteString(c.Email.Value)
h.WriteByte(low.HASH_PIPE)
}
// Note: Extensions are not included in the hash for Contact
return h.Sum64()
})
}

// GetExtensions returns all extensions for Contact
Expand Down
Loading