fix: continued moving everything to orderedmaps plus cleaned up most the tests

This commit is contained in:
Tristan Cartledge
2023-12-01 17:37:07 +00:00
parent 0f3d0cb28f
commit a4ad09aab3
169 changed files with 3435 additions and 3764 deletions

View File

@@ -21,7 +21,7 @@ import (
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"` PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"`
Mapping orderedmap.Map[string, string] `json:"mapping,omitempty" yaml:"mapping,omitempty"` Mapping *orderedmap.Map[string, string] `json:"mapping,omitempty" yaml:"mapping,omitempty"`
low *low.Discriminator low *low.Discriminator
} }

View File

@@ -127,7 +127,7 @@ func TestDynamicValue_MarshalYAMLInline(t *testing.T) {
// convert node into yaml // convert node into yaml
bits, _ := yaml.Marshal(rend) bits, _ := yaml.Marshal(rend)
assert.Equal(t, "properties:\n rice:\n $ref: '#/components/schemas/rice'", strings.TrimSpace(string(bits))) assert.Equal(t, "properties:\n rice:\n type: array\n items:\n type: string", strings.TrimSpace(string(bits)))
} }
func TestDynamicValue_MarshalYAMLInline_Error(t *testing.T) { func TestDynamicValue_MarshalYAMLInline_Error(t *testing.T) {

View File

@@ -17,9 +17,9 @@ import (
type Example struct { type Example struct {
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value any `json:"value,omitempty" yaml:"value,omitempty"` Value *yaml.Node `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"` ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Example low *low.Example
} }
@@ -58,7 +58,7 @@ func (e *Example) MarshalYAML() (interface{}, error) {
// ExtractExamples will convert a low-level example map, into a high level one that is simple to navigate. // ExtractExamples will convert a low-level example map, into a high level one that is simple to navigate.
// no fidelity is lost, everything is still available via GoLow() // no fidelity is lost, everything is still available via GoLow()
func ExtractExamples(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Example]]) orderedmap.Map[string, *Example] { func ExtractExamples(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Example]]) *orderedmap.Map[string, *Example] {
extracted := orderedmap.New[string, *Example]() extracted := orderedmap.New[string, *Example]()
for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() { for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, NewExample(pair.Value().Value)) extracted.Set(pair.Key().Value, NewExample(pair.Value().Value))

View File

@@ -17,7 +17,6 @@ import (
) )
func TestNewExample(t *testing.T) { func TestNewExample(t *testing.T) {
var cNode yaml.Node var cNode yaml.Node
yml := `summary: an example yml := `summary: an example
@@ -37,18 +36,23 @@ x-hack: code`
// build high // build high
highExample := NewExample(&lowExample) highExample := NewExample(&lowExample)
var xHack string
_ = highExample.Extensions.GetOrZero("x-hack").Decode(&xHack)
var example string
_ = highExample.Value.Decode(&example)
assert.Equal(t, "an example", highExample.Summary) assert.Equal(t, "an example", highExample.Summary)
assert.Equal(t, "something more", highExample.Description) assert.Equal(t, "something more", highExample.Description)
assert.Equal(t, "https://pb33f.io", highExample.ExternalValue) assert.Equal(t, "https://pb33f.io", highExample.ExternalValue)
assert.Equal(t, "code", highExample.Extensions["x-hack"]) assert.Equal(t, "code", xHack)
assert.Equal(t, "a thing", highExample.Value) assert.Equal(t, "a thing", example)
assert.Equal(t, 4, highExample.GoLow().ExternalValue.ValueNode.Line) assert.Equal(t, 4, highExample.GoLow().ExternalValue.ValueNode.Line)
assert.NotNil(t, highExample.GoLowUntyped()) assert.NotNil(t, highExample.GoLowUntyped())
// render the example as YAML // render the example as YAML
rendered, _ := highExample.Render() rendered, _ := highExample.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml) assert.Equal(t, yml, strings.TrimSpace(string(rendered)))
} }
func TestExtractExamples(t *testing.T) { func TestExtractExamples(t *testing.T) {
@@ -71,11 +75,9 @@ func TestExtractExamples(t *testing.T) {
) )
assert.Equal(t, "herbs", ExtractExamples(examplesMap).GetOrZero("green").Summary) assert.Equal(t, "herbs", ExtractExamples(examplesMap).GetOrZero("green").Summary)
} }
func ExampleNewExample() { func ExampleNewExample() {
// create some example yaml (or can be JSON, it does not matter) // create some example yaml (or can be JSON, it does not matter)
yml := `summary: something interesting yml := `summary: something interesting
description: something more interesting with detail description: something more interesting with detail
@@ -98,5 +100,4 @@ x-hack: code`
fmt.Print(highExample.ExternalValue) fmt.Print(highExample.ExternalValue)
// Output: https://pb33f.io // Output: https://pb33f.io
} }

View File

@@ -6,6 +6,7 @@ package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -18,7 +19,7 @@ import (
type ExternalDoc struct { type ExternalDoc struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.ExternalDoc low *low.ExternalDoc
} }
@@ -46,7 +47,7 @@ func (e *ExternalDoc) GoLowUntyped() any {
return e.low return e.low
} }
func (e *ExternalDoc) GetExtensions() map[string]any { func (e *ExternalDoc) GetExtensions() *orderedmap.Map[string, *yaml.Node] {
return e.Extensions return e.Extensions
} }

View File

@@ -5,17 +5,17 @@ package base
import ( import (
"context" "context"
"fmt"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings" "strings"
"testing" "testing"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
) )
func TestNewExternalDoc(t *testing.T) { func TestNewExternalDoc(t *testing.T) {
var cNode yaml.Node var cNode yaml.Node
yml := `description: hack code yml := `description: hack code
@@ -31,22 +31,23 @@ x-hack: code`
highExt := NewExternalDoc(&lowExt) highExt := NewExternalDoc(&lowExt)
var xHack string
_ = highExt.Extensions.GetOrZero("x-hack").Decode(&xHack)
assert.Equal(t, "hack code", highExt.Description) assert.Equal(t, "hack code", highExt.Description)
assert.Equal(t, "https://pb33f.io", highExt.URL) assert.Equal(t, "https://pb33f.io", highExt.URL)
assert.Equal(t, "code", highExt.Extensions["x-hack"]) assert.Equal(t, "code", xHack)
wentLow := highExt.GoLow() wentLow := highExt.GoLow()
assert.Equal(t, 2, wentLow.URL.ValueNode.Line) assert.Equal(t, 2, wentLow.URL.ValueNode.Line)
assert.Len(t, highExt.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(highExt.GetExtensions()))
// render the high-level object as YAML // render the high-level object as YAML
rendered, _ := highExt.Render() rendered, _ := highExt.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml) assert.Equal(t, strings.TrimSpace(string(rendered)), yml)
} }
func ExampleNewExternalDoc() { func TestExampleNewExternalDoc(t *testing.T) {
// create a new external documentation spec reference // create a new external documentation spec reference
// this can be YAML or JSON. // this can be YAML or JSON.
yml := `description: hack code docs yml := `description: hack code docs
@@ -67,7 +68,8 @@ x-hack: code`
// create new high-level ExternalDoc // create new high-level ExternalDoc
highExt := NewExternalDoc(&lowExt) highExt := NewExternalDoc(&lowExt)
// print out a extension var xHack string
fmt.Print(highExt.Extensions["x-hack"]) _ = highExt.Extensions.GetOrZero("x-hack").Decode(&xHack)
// Output: code
assert.Equal(t, "code", xHack)
} }

View File

@@ -6,6 +6,7 @@ package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -24,7 +25,7 @@ type Info struct {
Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"` Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
License *License `json:"license,omitempty" yaml:"license,omitempty"` License *License `json:"license,omitempty" yaml:"license,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"` Version string `json:"version,omitempty" yaml:"version,omitempty"`
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Info low *low.Info
} }
@@ -53,7 +54,7 @@ func NewInfo(info *low.Info) *Info {
if !info.Version.IsEmpty() { if !info.Version.IsEmpty() {
i.Version = info.Version.Value i.Version = info.Version.Value
} }
if len(info.Extensions) > 0 { if orderedmap.Len(info.Extensions) > 0 {
i.Extensions = high.ExtractExtensions(info.Extensions) i.Extensions = high.ExtractExtensions(info.Extensions)
} }
return i return i

View File

@@ -10,6 +10,8 @@ import (
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -37,6 +39,9 @@ x-cli-name: chicken cli`
highInfo := NewInfo(&lowInfo) highInfo := NewInfo(&lowInfo)
var xCliName string
_ = highInfo.Extensions.GetOrZero("x-cli-name").Decode(&xCliName)
assert.Equal(t, "chicken", highInfo.Title) assert.Equal(t, "chicken", highInfo.Title)
assert.Equal(t, "a chicken nugget", highInfo.Summary) assert.Equal(t, "a chicken nugget", highInfo.Summary)
assert.Equal(t, "nugget", highInfo.Description) assert.Equal(t, "nugget", highInfo.Description)
@@ -45,7 +50,7 @@ x-cli-name: chicken cli`
assert.Equal(t, "pb33f", highInfo.License.Name) assert.Equal(t, "pb33f", highInfo.License.Name)
assert.Equal(t, "https://pb33f.io", highInfo.License.URL) assert.Equal(t, "https://pb33f.io", highInfo.License.URL)
assert.Equal(t, "99.99", highInfo.Version) assert.Equal(t, "99.99", highInfo.Version)
assert.Equal(t, "chicken cli", highInfo.Extensions["x-cli-name"]) assert.Equal(t, "chicken cli", xCliName)
wentLow := highInfo.GoLow() wentLow := highInfo.GoLow()
assert.Equal(t, 10, wentLow.Version.ValueNode.Line) assert.Equal(t, 10, wentLow.Version.ValueNode.Line)
@@ -109,13 +114,12 @@ url: https://opensource.org/licenses/MIT`
} }
func TestInfo_Render(t *testing.T) { func TestInfo_Render(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext := make(map[string]any) ext.Set("x-pizza", utils.CreateStringNode("pepperoni"))
ext["x-pizza"] = "pepperoni" ext.Set("x-cake", utils.CreateYamlNode(&License{
ext["x-cake"] = &License{
Name: "someone", Name: "someone",
URL: "nowhere", URL: "nowhere",
} }))
highI := &Info{ highI := &Info{
Title: "hey", Title: "hey",
Description: "there you", Description: "there you",
@@ -146,6 +150,9 @@ func TestInfo_Render(t *testing.T) {
// build high // build high
highInfo := NewInfo(&lowInfo) highInfo := NewInfo(&lowInfo)
var xPizza string
_ = highInfo.Extensions.GetOrZero("x-pizza").Decode(&xPizza)
assert.Equal(t, "hey", highInfo.Title) assert.Equal(t, "hey", highInfo.Title)
assert.Equal(t, "there you", highInfo.Description) assert.Equal(t, "there you", highInfo.Description)
assert.Equal(t, "have you got any money", highInfo.TermsOfService) assert.Equal(t, "have you got any money", highInfo.TermsOfService)
@@ -154,12 +161,11 @@ func TestInfo_Render(t *testing.T) {
assert.Equal(t, "MIT", highInfo.License.Name) assert.Equal(t, "MIT", highInfo.License.Name)
assert.Equal(t, "https://opensource.org/licenses/MIT", highInfo.License.URL) assert.Equal(t, "https://opensource.org/licenses/MIT", highInfo.License.URL)
assert.Equal(t, "1.2.3", highInfo.Version) assert.Equal(t, "1.2.3", highInfo.Version)
assert.Equal(t, "pepperoni", highInfo.Extensions["x-pizza"]) assert.Equal(t, "pepperoni", xPizza)
assert.NotNil(t, highInfo.GoLowUntyped()) assert.NotNil(t, highInfo.GoLowUntyped())
} }
func TestInfo_RenderOrder(t *testing.T) { func TestInfo_RenderOrder(t *testing.T) {
yml := `title: hey yml := `title: hey
description: there you description: there you
termsOfService: have you got any money termsOfService: have you got any money
@@ -187,6 +193,9 @@ x-cake:
// build high // build high
highInfo := NewInfo(&lowInfo) highInfo := NewInfo(&lowInfo)
var xPizza string
_ = highInfo.Extensions.GetOrZero("x-pizza").Decode(&xPizza)
assert.Equal(t, "hey", highInfo.Title) assert.Equal(t, "hey", highInfo.Title)
assert.Equal(t, "there you", highInfo.Description) assert.Equal(t, "there you", highInfo.Description)
assert.Equal(t, "have you got any money", highInfo.TermsOfService) assert.Equal(t, "have you got any money", highInfo.TermsOfService)
@@ -195,7 +204,7 @@ x-cake:
assert.Equal(t, "MIT", highInfo.License.Name) assert.Equal(t, "MIT", highInfo.License.Name)
assert.Equal(t, "https://opensource.org/licenses/MIT", highInfo.License.URL) assert.Equal(t, "https://opensource.org/licenses/MIT", highInfo.License.URL)
assert.Equal(t, "1.2.3", highInfo.Version) assert.Equal(t, "1.2.3", highInfo.Version)
assert.Equal(t, "pepperoni", highInfo.Extensions["x-pizza"]) assert.Equal(t, "pepperoni", xPizza)
// marshal high back to yaml, should be the same as the original, in same order. // marshal high back to yaml, should be the same as the original, in same order.
bytes, _ := highInfo.Render() bytes, _ := highInfo.Render()

View File

@@ -45,7 +45,7 @@ type Schema struct {
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
// in 3.1 examples can be an array (which is recommended) // in 3.1 examples can be an array (which is recommended)
Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"` Examples []*yaml.Node `json:"examples,omitempty" yaml:"examples,omitempty"`
// in 3.1 prefixItems provides tuple validation support. // in 3.1 prefixItems provides tuple validation support.
PrefixItems []*SchemaProxy `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` PrefixItems []*SchemaProxy `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"`
@@ -57,8 +57,8 @@ type Schema struct {
If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"` If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"`
Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"` Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"`
Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"` Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"`
DependentSchemas orderedmap.Map[string, *SchemaProxy] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` DependentSchemas *orderedmap.Map[string, *SchemaProxy] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"`
PatternProperties orderedmap.Map[string, *SchemaProxy] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` PatternProperties *orderedmap.Map[string, *SchemaProxy] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"`
PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"`
UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"`
@@ -74,7 +74,7 @@ type Schema struct {
// Compatible with all versions // Compatible with all versions
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"` Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Properties orderedmap.Map[string, *SchemaProxy] `json:"properties,omitempty" yaml:"properties,omitempty"` Properties *orderedmap.Map[string, *SchemaProxy] `json:"properties,omitempty" yaml:"properties,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"` Title string `json:"title,omitempty" yaml:"title,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *float64 `json:"maximum,renderZero,omitempty" yaml:"maximum,renderZero,omitempty"` Maximum *float64 `json:"maximum,renderZero,omitempty" yaml:"maximum,renderZero,omitempty"`
@@ -89,19 +89,19 @@ type Schema struct {
MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"` Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` Enum []*yaml.Node `json:"enum,omitempty" yaml:"enum,omitempty"`
AdditionalProperties *DynamicValue[*SchemaProxy, bool] `json:"additionalProperties,renderZero,omitempty" yaml:"additionalProperties,renderZero,omitempty"` AdditionalProperties *DynamicValue[*SchemaProxy, bool] `json:"additionalProperties,renderZero,omitempty" yaml:"additionalProperties,renderZero,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"` Default *yaml.Node `json:"default,omitempty" yaml:"default,renderZero,omitempty"`
Const any `json:"const,omitempty" yaml:"const,renderZero,omitempty"` Const *yaml.Node `json:"const,omitempty" yaml:"const,renderZero,omitempty"`
Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 ReadOnly *bool `json:"readOnly,renderZero,omitempty" yaml:"readOnly,renderZero,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 WriteOnly *bool `json:"writeOnly,renderZero,omitempty" yaml:"writeOnly,renderZero,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"` Example *yaml.Node `json:"example,omitempty" yaml:"example,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *base.Schema low *base.Schema
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. // Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
@@ -266,17 +266,17 @@ func NewSchema(schema *base.Schema) *Schema {
s.Nullable = &schema.Nullable.Value s.Nullable = &schema.Nullable.Value
} }
if !schema.ReadOnly.IsEmpty() { if !schema.ReadOnly.IsEmpty() {
s.ReadOnly = schema.ReadOnly.Value s.ReadOnly = &schema.ReadOnly.Value
} }
if !schema.WriteOnly.IsEmpty() { if !schema.WriteOnly.IsEmpty() {
s.WriteOnly = schema.WriteOnly.Value s.WriteOnly = &schema.WriteOnly.Value
} }
if !schema.Deprecated.IsEmpty() { if !schema.Deprecated.IsEmpty() {
s.Deprecated = &schema.Deprecated.Value s.Deprecated = &schema.Deprecated.Value
} }
s.Example = schema.Example.Value s.Example = schema.Example.Value
if len(schema.Examples.Value) > 0 { if len(schema.Examples.Value) > 0 {
examples := make([]any, len(schema.Examples.Value)) examples := make([]*yaml.Node, len(schema.Examples.Value))
for i := 0; i < len(schema.Examples.Value); i++ { for i := 0; i < len(schema.Examples.Value); i++ {
examples[i] = schema.Examples.Value[i].Value examples[i] = schema.Examples.Value[i].Value
} }
@@ -298,11 +298,11 @@ func NewSchema(schema *base.Schema) *Schema {
} }
s.Required = req s.Required = req
var enum []any
if !schema.Anchor.IsEmpty() { if !schema.Anchor.IsEmpty() {
s.Anchor = schema.Anchor.Value s.Anchor = schema.Anchor.Value
} }
var enum []*yaml.Node
for i := range schema.Enum.Value { for i := range schema.Enum.Value {
enum = append(enum, schema.Enum.Value[i].Value) enum = append(enum, schema.Enum.Value[i].Value)
} }
@@ -322,11 +322,13 @@ func NewSchema(schema *base.Schema) *Schema {
// for every item, build schema async // for every item, build schema async
buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], idx int, bChan chan buildResult) { buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], idx int, bChan chan buildResult) {
p := NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{ n := &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: sch.ValueNode, ValueNode: sch.GetValueNode(),
Value: sch.Value, Value: sch.Value,
Reference: sch.GetReference(), }
}) n.SetReference(sch.GetReference(), sch.GetValueNode())
p := NewSchemaProxy(n)
bChan <- buildResult{idx: idx, s: p} bChan <- buildResult{idx: idx, s: p}
} }
@@ -353,7 +355,7 @@ func NewSchema(schema *base.Schema) *Schema {
// props async // props async
buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy],
props orderedmap.Map[string, *SchemaProxy], sw int, props *orderedmap.Map[string, *SchemaProxy], sw int,
) { ) {
props.Set(k.Value, NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{ props.Set(k.Value, NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
Value: v.Value, Value: v.Value,

View File

@@ -4,13 +4,14 @@
package base package base
import ( import (
"sync"
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sync"
) )
// SchemaProxy exists as a stub that will create a Schema once (and only once) the Schema() method is called. An // SchemaProxy exists as a stub that will create a Schema once (and only once) the Schema() method is called. An
@@ -98,11 +99,15 @@ func (sp *SchemaProxy) Schema() *Schema {
// IsReference returns true if the SchemaProxy is a reference to another Schema. // IsReference returns true if the SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) IsReference() bool { func (sp *SchemaProxy) IsReference() bool {
if sp == nil {
return false
}
if sp.refStr != "" { if sp.refStr != "" {
return true return true
} }
if sp.schema != nil { if sp.schema != nil {
return sp.schema.Value.IsSchemaReference() return sp.schema.Value.IsReference()
} }
return false return false
} }
@@ -112,7 +117,14 @@ func (sp *SchemaProxy) GetReference() string {
if sp.refStr != "" { if sp.refStr != "" {
return sp.refStr return sp.refStr
} }
return sp.schema.Value.GetSchemaReference() return sp.schema.GetValue().GetReference()
}
func (sp *SchemaProxy) GetReferenceNode() *yaml.Node {
if sp.refStr != "" {
return nil
}
return sp.schema.GetValue().GetReferenceNode()
} }
// GetReferenceOrigin returns a pointer to the index.NodeOrigin of the $ref if this SchemaProxy is a reference to another Schema. // GetReferenceOrigin returns a pointer to the index.NodeOrigin of the $ref if this SchemaProxy is a reference to another Schema.
@@ -171,12 +183,13 @@ func (sp *SchemaProxy) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil return nb.Render(), nil
} else { } else {
refNode := sp.GetReferenceNode()
if refNode != nil {
return refNode, nil
}
// do not build out a reference, just marshal the reference. // do not build out a reference, just marshal the reference.
mp := utils.CreateEmptyMapNode() return utils.CreateRefNode(sp.GetReference()), nil
mp.Content = append(mp.Content,
utils.CreateStringNode("$ref"),
utils.CreateStringNode(sp.GetReference()))
return mp, nil
} }
} }

View File

@@ -306,8 +306,8 @@ $anchor: anchor`
assert.Equal(t, "string", compiled.PropertyNames.Schema().Type[0]) assert.Equal(t, "string", compiled.PropertyNames.Schema().Type[0])
assert.Equal(t, "boolean", compiled.UnevaluatedItems.Schema().Type[0]) assert.Equal(t, "boolean", compiled.UnevaluatedItems.Schema().Type[0])
assert.Equal(t, "integer", compiled.UnevaluatedProperties.A.Schema().Type[0]) assert.Equal(t, "integer", compiled.UnevaluatedProperties.A.Schema().Type[0])
assert.True(t, compiled.ReadOnly) assert.True(t, *compiled.ReadOnly)
assert.True(t, compiled.WriteOnly) assert.True(t, *compiled.WriteOnly)
assert.True(t, *compiled.Deprecated) assert.True(t, *compiled.Deprecated)
assert.True(t, *compiled.Nullable) assert.True(t, *compiled.Nullable)
assert.Equal(t, "anchor", compiled.Anchor) assert.Equal(t, "anchor", compiled.Anchor)
@@ -548,7 +548,7 @@ func TestSchemaProxy_GoLow(t *testing.T) {
sp := NewSchemaProxy(&lowRef) sp := NewSchemaProxy(&lowRef)
assert.Equal(t, lowProxy, sp.GoLow()) assert.Equal(t, lowProxy, sp.GoLow())
assert.Equal(t, ref, sp.GoLow().GetSchemaReference()) assert.Equal(t, ref, sp.GoLow().GetReference())
assert.Equal(t, ref, sp.GoLow().GetReference()) assert.Equal(t, ref, sp.GoLow().GetReference())
spNil := NewSchemaProxy(nil) spNil := NewSchemaProxy(nil)
@@ -703,7 +703,14 @@ examples:
` `
highSchema := getHighSchema(t, yml) highSchema := getHighSchema(t, yml)
assert.Equal(t, []any{int64(5), int64(10)}, highSchema.Examples) examples := []any{}
for _, ex := range highSchema.Examples {
var v int64
assert.NoError(t, ex.Decode(&v))
examples = append(examples, v)
}
assert.Equal(t, []any{int64(5), int64(10)}, examples)
} }
func ExampleNewSchema() { func ExampleNewSchema() {
@@ -1123,7 +1130,7 @@ components:
// now render it out, it should be identical. // now render it out, it should be identical.
schemaBytes, _ := compiled.RenderInline() schemaBytes, _ := compiled.RenderInline()
assert.Len(t, schemaBytes, 585) assert.Equal(t, "properties:\n bigBank:\n type: object\n properties:\n failure_balance_transaction:\n allOf:\n - type: object\n properties:\n name:\n type: string\n price:\n type: number\n anyOf:\n - description: A balance transaction\n anyOf:\n - maxLength: 5000\n type: string\n - description: A balance transaction\n", string(schemaBytes))
} }
func TestUnevaluatedPropertiesBoolean_True(t *testing.T) { func TestUnevaluatedPropertiesBoolean_True(t *testing.T) {

View File

@@ -21,7 +21,7 @@ import (
// The name used for each property MUST correspond to a security scheme declared in the Security Definitions // The name used for each property MUST correspond to a security scheme declared in the Security Definitions
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityRequirement struct { type SecurityRequirement struct {
Requirements orderedmap.Map[string, []string] `json:"-" yaml:"-"` Requirements *orderedmap.Map[string, []string] `json:"-" yaml:"-"`
low *base.SecurityRequirement low *base.SecurityRequirement
} }
@@ -59,7 +59,6 @@ func (s *SecurityRequirement) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the SecurityRequirement object. // MarshalYAML will create a ready to render YAML representation of the SecurityRequirement object.
func (s *SecurityRequirement) MarshalYAML() (interface{}, error) { func (s *SecurityRequirement) MarshalYAML() (interface{}, error) {
type req struct { type req struct {
line int line int
key string key string

View File

@@ -6,6 +6,7 @@ package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -19,7 +20,7 @@ type Tag struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Tag low *low.Tag
} }

View File

@@ -16,7 +16,6 @@ import (
) )
func TestNewTag(t *testing.T) { func TestNewTag(t *testing.T) {
var cNode yaml.Node var cNode yaml.Node
yml := `name: chicken yml := `name: chicken
@@ -33,10 +32,13 @@ x-hack: code`
highTag := NewTag(&lowTag) highTag := NewTag(&lowTag)
var xHack string
_ = highTag.Extensions.GetOrZero("x-hack").Decode(&xHack)
assert.Equal(t, "chicken", highTag.Name) assert.Equal(t, "chicken", highTag.Name)
assert.Equal(t, "nuggets", highTag.Description) assert.Equal(t, "nuggets", highTag.Description)
assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL)
assert.Equal(t, "code", highTag.Extensions["x-hack"]) assert.Equal(t, "code", xHack)
wentLow := highTag.GoLow() wentLow := highTag.GoLow()
assert.Equal(t, 5, wentLow.FindExtension("x-hack").ValueNode.Line) assert.Equal(t, 5, wentLow.FindExtension("x-hack").ValueNode.Line)
@@ -45,11 +47,9 @@ x-hack: code`
// render the tag as YAML // render the tag as YAML
highTagBytes, _ := highTag.Render() highTagBytes, _ := highTag.Render()
assert.Equal(t, strings.TrimSpace(string(highTagBytes)), yml) assert.Equal(t, strings.TrimSpace(string(highTagBytes)), yml)
} }
func TestTag_RenderInline(t *testing.T) { func TestTag_RenderInline(t *testing.T) {
tag := &Tag{ tag := &Tag{
Name: "cake", Name: "cake",
} }

View File

@@ -6,6 +6,7 @@ package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -25,7 +26,7 @@ type XML struct {
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"` Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"` Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.XML low *low.XML
} }

View File

@@ -11,27 +11,18 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/pb33f/libopenapi/datamodel/high/nodes"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// NodeEntry represents a single node used by NodeBuilder.
type NodeEntry struct {
Tag string
Key string
Value any
StringValue string
Line int
Style yaml.Style
RenderZero bool
}
// NodeBuilder is a structure used by libopenapi high-level objects, to render themselves back to YAML. // NodeBuilder is a structure used by libopenapi high-level objects, to render themselves back to YAML.
// this allows high-level objects to be 'mutable' because all changes will be rendered out. // this allows high-level objects to be 'mutable' because all changes will be rendered out.
type NodeBuilder struct { type NodeBuilder struct {
Version float32 Version float32
Nodes []*NodeEntry Nodes []*nodes.NodeEntry
High any High any
Low any Low any
Resolve bool // If set to true, all references will be rendered inline Resolve bool // If set to true, all references will be rendered inline
@@ -62,7 +53,6 @@ func NewNodeBuilder(high any, low any) *NodeBuilder {
} }
func (n *NodeBuilder) add(key string, i int) { func (n *NodeBuilder) add(key string, i int) {
// only operate on exported fields. // only operate on exported fields.
if unicode.IsLower(rune(key[0])) { if unicode.IsLower(rune(key[0])) {
return return
@@ -71,38 +61,38 @@ func (n *NodeBuilder) add(key string, i int) {
// if the key is 'Extensions' then we need to extract the keys from the map // if the key is 'Extensions' then we need to extract the keys from the map
// and add them to the node builder. // and add them to the node builder.
if key == "Extensions" { if key == "Extensions" {
extensions := reflect.ValueOf(n.High).Elem().FieldByName(key) ev := reflect.ValueOf(n.High).Elem().FieldByName(key).Interface()
for b, e := range extensions.MapKeys() { var extensions *orderedmap.Map[string, *yaml.Node]
v := extensions.MapIndex(e) if ev != nil {
extensions = ev.(*orderedmap.Map[string, *yaml.Node])
extKey := e.String() }
extValue := v.Interface()
nodeEntry := &NodeEntry{Tag: extKey, Key: extKey, Value: extValue, Line: 9999 + b}
var lowExtensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
if n.Low != nil && !reflect.ValueOf(n.Low).IsZero() { if n.Low != nil && !reflect.ValueOf(n.Low).IsZero() {
fieldValue := reflect.ValueOf(n.Low).Elem().FieldByName("Extensions")
f := fieldValue.Interface()
value := reflect.ValueOf(f)
switch value.Kind() {
case reflect.Map:
if j, ok := n.Low.(low.HasExtensionsUntyped); ok { if j, ok := n.Low.(low.HasExtensionsUntyped); ok {
originalExtensions := j.GetExtensions() lowExtensions = j.GetExtensions()
u := 0
for k := range originalExtensions {
if k.Value == extKey {
if originalExtensions[k].ValueNode.Line != 0 {
nodeEntry.Style = originalExtensions[k].ValueNode.Style
nodeEntry.Line = originalExtensions[k].ValueNode.Line + u
} else {
nodeEntry.Line = 999999 + b + u
} }
} }
u++
j := 0
if lowExtensions != nil {
// If we have low extensions get the original lowest line number so we end up in the same place
for pair := orderedmap.First(lowExtensions); pair != nil; pair = pair.Next() {
if j == 0 || pair.Key().KeyNode.Line < j {
j = pair.Key().KeyNode.Line
} }
} }
} }
for pair := orderedmap.First(extensions); pair != nil; pair = pair.Next() {
nodeEntry := &nodes.NodeEntry{Tag: pair.Key(), Key: pair.Key(), Value: pair.Value(), Line: j}
if lowExtensions != nil {
lowItem := low.FindItemInOrderedMap(pair.Key(), lowExtensions)
nodeEntry.LowValue = lowItem
} }
n.Nodes = append(n.Nodes, nodeEntry) n.Nodes = append(n.Nodes, nodeEntry)
j++
} }
// done, extensions are handled separately. // done, extensions are handled separately.
return return
@@ -119,11 +109,11 @@ func (n *NodeBuilder) add(key string, i int) {
var renderZeroFlag, omitemptyFlag bool var renderZeroFlag, omitemptyFlag bool
tagParts := strings.Split(tag, ",") tagParts := strings.Split(tag, ",")
for i = 1; i < len(tagParts); i++ { for _, part := range tagParts {
if tagParts[i] == renderZero { if part == renderZero {
renderZeroFlag = true renderZeroFlag = true
} }
if tagParts[i] == "omitempty" { if part == "omitempty" {
omitemptyFlag = true omitemptyFlag = true
} }
} }
@@ -133,7 +123,9 @@ func (n *NodeBuilder) add(key string, i int) {
f := fieldValue.Interface() f := fieldValue.Interface()
value := reflect.ValueOf(f) value := reflect.ValueOf(f)
var isZero bool var isZero bool
if zeroer, ok := f.(yaml.IsZeroer); ok && zeroer.IsZero() { if (value.Kind() == reflect.Interface || value.Kind() == reflect.Ptr) && value.IsNil() {
isZero = true
} else if zeroer, ok := f.(yaml.IsZeroer); ok && zeroer.IsZero() {
isZero = true isZero = true
} else if f == nil || value.IsZero() { } else if f == nil || value.IsZero() {
isZero = true isZero = true
@@ -146,7 +138,7 @@ func (n *NodeBuilder) add(key string, i int) {
} }
// create a new node entry // create a new node entry
nodeEntry := &NodeEntry{Tag: tagName, Key: key} nodeEntry := &nodes.NodeEntry{Tag: tagName, Key: key}
nodeEntry.RenderZero = renderZeroFlag nodeEntry.RenderZero = renderZeroFlag
switch value.Kind() { switch value.Kind() {
case reflect.Float64, reflect.Float32: case reflect.Float64, reflect.Float32:
@@ -192,39 +184,24 @@ func (n *NodeBuilder) add(key string, i int) {
fLow := lowFieldValue.Interface() fLow := lowFieldValue.Interface()
value = reflect.ValueOf(fLow) value = reflect.ValueOf(fLow)
type lineStyle struct { nodeEntry.LowValue = fLow
line int
style yaml.Style
}
switch value.Kind() { switch value.Kind() {
case reflect.Slice: case reflect.Slice:
l := value.Len() l := value.Len()
lines := make([]lineStyle, l) lines := make([]int, l)
for g := 0; g < l; g++ { for g := 0; g < l; g++ {
qw := value.Index(g).Interface() qw := value.Index(g).Interface()
if we, wok := qw.(low.HasKeyNode); wok { if we, wok := qw.(low.HasKeyNode); wok {
lines[g] = lineStyle{we.GetKeyNode().Line, we.GetKeyNode().Style} lines[g] = we.GetKeyNode().Line
} }
} }
sort.Slice(lines, func(i, j int) bool { sort.Slice(lines, func(i, j int) bool {
return lines[i].line < lines[j].line return lines[i] < lines[j]
}) })
nodeEntry.Line = lines[0].line // pick the lowest line number so this key is sorted in order. nodeEntry.Line = lines[0]
nodeEntry.Style = lines[0].style
break
case reflect.Map: case reflect.Map:
l := value.Len() panic("only ordered maps are supported")
line := make([]int, l)
for q, ky := range value.MapKeys() {
if we, wok := ky.Interface().(low.HasKeyNode); wok {
line[q] = we.GetKeyNode().Line
}
}
sort.Ints(line)
nodeEntry.Line = line[0]
case reflect.Struct: case reflect.Struct:
y := value.Interface() y := value.Interface()
nodeEntry.Line = 9999 + i nodeEntry.Line = 9999 + i
@@ -232,13 +209,11 @@ func (n *NodeBuilder) add(key string, i int) {
if nb.IsReference() { if nb.IsReference() {
if jk, kj := y.(low.HasKeyNode); kj { if jk, kj := y.(low.HasKeyNode); kj {
nodeEntry.Line = jk.GetKeyNode().Line nodeEntry.Line = jk.GetKeyNode().Line
nodeEntry.Style = jk.GetKeyNode().Style
break break
} }
} }
if nb.GetValueNode() != nil { if nb.GetValueNode() != nil {
nodeEntry.Line = nb.GetValueNode().Line nodeEntry.Line = nb.GetValueNode().Line
nodeEntry.Style = nb.GetValueNode().Style
} }
} }
default: default:
@@ -252,12 +227,13 @@ func (n *NodeBuilder) add(key string, i int) {
} }
} }
func (n *NodeBuilder) renderReference() []*yaml.Node { func (n *NodeBuilder) renderReference(fg low.IsReferenced) *yaml.Node {
fg := n.Low.(low.IsReferenced) origNode := fg.GetReferenceNode()
nodes := make([]*yaml.Node, 2) if origNode == nil {
nodes[0] = utils.CreateStringNode("$ref") return utils.CreateRefNode(fg.GetReference())
nodes[1] = utils.CreateStringNode(fg.GetReference()) }
return nodes
return origNode
} }
// Render will render the NodeBuilder back to a YAML node, iterating over every NodeEntry defined // Render will render the NodeBuilder back to a YAML node, iterating over every NodeEntry defined
@@ -272,8 +248,7 @@ func (n *NodeBuilder) Render() *yaml.Node {
g := reflect.ValueOf(fg) g := reflect.ValueOf(fg)
if !g.IsNil() { if !g.IsNil() {
if fg.IsReference() && !n.Resolve { if fg.IsReference() && !n.Resolve {
m.Content = append(m.Content, n.renderReference()...) return n.renderReference(n.Low.(low.IsReferenced))
return m
} }
} }
} }
@@ -295,7 +270,7 @@ func (n *NodeBuilder) Render() *yaml.Node {
// AddYAMLNode will add a new *yaml.Node to the parent node, using the tag, key and value provided. // AddYAMLNode will add a new *yaml.Node to the parent node, using the tag, key and value provided.
// If the value is nil, then the node will not be added. This method is recursive, so it will dig down // If the value is nil, then the node will not be added. This method is recursive, so it will dig down
// into any non-scalar types. // into any non-scalar types.
func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Node { func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *yaml.Node {
if entry.Value == nil { if entry.Value == nil {
return parent return parent
} }
@@ -305,11 +280,11 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
var l *yaml.Node var l *yaml.Node
if entry.Tag != "" { if entry.Tag != "" {
l = utils.CreateStringNode(entry.Tag) l = utils.CreateStringNode(entry.Tag)
l.Style = entry.KeyStyle
} }
value := entry.Value value := entry.Value
line := entry.Line line := entry.Line
key := entry.Key
var valueNode *yaml.Node var valueNode *yaml.Node
switch t.Kind() { switch t.Kind() {
@@ -318,9 +293,15 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
val := value.(string) val := value.(string)
valueNode = utils.CreateStringNode(val) valueNode = utils.CreateStringNode(val)
valueNode.Line = line valueNode.Line = line
valueNode.Style = entry.Style
break
if entry.LowValue != nil {
if vnut, ok := entry.LowValue.(low.HasValueNodeUntyped); ok {
vn := vnut.GetValueNode()
if vn != nil {
valueNode.Style = vn.Style
}
}
}
case reflect.Bool: case reflect.Bool:
val := value.(bool) val := value.(bool)
if !val { if !val {
@@ -329,26 +310,18 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
valueNode = utils.CreateBoolNode("true") valueNode = utils.CreateBoolNode("true")
} }
valueNode.Line = line valueNode.Line = line
break
case reflect.Int: case reflect.Int:
val := strconv.Itoa(value.(int)) val := strconv.Itoa(value.(int))
valueNode = utils.CreateIntNode(val) valueNode = utils.CreateIntNode(val)
valueNode.Line = line valueNode.Line = line
break
case reflect.Int64: case reflect.Int64:
val := strconv.FormatInt(value.(int64), 10) val := strconv.FormatInt(value.(int64), 10)
valueNode = utils.CreateIntNode(val) valueNode = utils.CreateIntNode(val)
valueNode.Line = line valueNode.Line = line
break
case reflect.Float32: case reflect.Float32:
val := strconv.FormatFloat(float64(value.(float32)), 'f', 2, 64) val := strconv.FormatFloat(float64(value.(float32)), 'f', 2, 64)
valueNode = utils.CreateFloatNode(val) valueNode = utils.CreateFloatNode(val)
valueNode.Line = line valueNode.Line = line
break
case reflect.Float64: case reflect.Float64:
precision := -1 precision := -1
if entry.StringValue != "" && strings.Contains(entry.StringValue, ".") { if entry.StringValue != "" && strings.Contains(entry.StringValue, ".") {
@@ -357,87 +330,8 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
val := strconv.FormatFloat(value.(float64), 'f', precision, 64) val := strconv.FormatFloat(value.(float64), 'f', precision, 64)
valueNode = utils.CreateFloatNode(val) valueNode = utils.CreateFloatNode(val)
valueNode.Line = line valueNode.Line = line
break
case reflect.Map: case reflect.Map:
panic("only ordered maps are supported")
// the keys will be rendered randomly, if we don't find out the original line
// number of the tag.
var orderedCollection []*NodeEntry
m := reflect.ValueOf(value)
for g, k := range m.MapKeys() {
var x string
// extract key
yu := k.Interface()
if o, ok := yu.(low.HasKeyNode); ok {
x = o.GetKeyNode().Value
} else {
x = k.String()
}
// go low and pull out the line number.
lowProps := reflect.ValueOf(n.Low)
if n.Low != nil && !lowProps.IsZero() && !lowProps.IsNil() {
gu := lowProps.Elem()
gi := gu.FieldByName(key)
jl := reflect.ValueOf(gi)
if !jl.IsZero() && gi.Interface() != nil {
gh := gi.Interface()
// extract low level key line number
if pr, ok := gh.(low.HasValueUnTyped); ok {
fg := reflect.ValueOf(pr.GetValueUntyped())
found := false
found, orderedCollection = n.extractLowMapKeys(fg, x, found, orderedCollection, m, k)
if found != true {
// this is something new, add it.
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
} else {
// this is a map, but it may be wrapped still.
bj := reflect.ValueOf(gh)
orderedCollection = n.extractLowMapKeysWrapped(bj, x, orderedCollection, g)
}
} else {
// this is a map, without any low level details available (probably an extension map).
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
} else {
// this is a map, without any low level details available (probably an extension map).
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
}
// sort the slice by line number to ensure everything is rendered in order.
sort.Slice(orderedCollection, func(i, j int) bool {
return orderedCollection[i].Line < orderedCollection[j].Line
})
// create an empty map.
p := utils.CreateEmptyMapNode()
// build out each map node in original order.
for _, cv := range orderedCollection {
n.AddYAMLNode(p, cv)
}
if len(p.Content) > 0 {
valueNode = p
}
case reflect.Slice: case reflect.Slice:
@@ -456,8 +350,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
if ut != nil && r.GetReference() != "" && if ut != nil && r.GetReference() != "" &&
ut.(low.IsReferenced).IsReference() { ut.(low.IsReferenced).IsReference() {
if !n.Resolve { if !n.Resolve {
refNode := utils.CreateRefNode(glu.GoLowUntyped().(low.IsReferenced).GetReference()) sl.Content = append(sl.Content, n.renderReference(glu.GoLowUntyped().(low.IsReferenced)))
sl.Content = append(sl.Content, refNode)
skip = true skip = true
} else { } else {
skip = false skip = false
@@ -472,13 +365,13 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
if er, ko := sqi.(Renderable); ko { if er, ko := sqi.(Renderable); ko {
var rend interface{} var rend interface{}
if !n.Resolve { if !n.Resolve {
rend, _ = er.(Renderable).MarshalYAML() rend, _ = er.MarshalYAML()
} else { } else {
// try and render inline, if we can, otherwise treat as normal. // try and render inline, if we can, otherwise treat as normal.
if _, ko := er.(RenderableInline); ko { if _, ko := er.(RenderableInline); ko {
rend, _ = er.(RenderableInline).MarshalYAMLInline() rend, _ = er.(RenderableInline).MarshalYAMLInline()
} else { } else {
rend, _ = er.(Renderable).MarshalYAML() rend, _ = er.MarshalYAML()
} }
} }
// check if this is a pointer or not. // check if this is a pointer or not.
@@ -505,6 +398,17 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
if err != nil { if err != nil {
return parent return parent
} else { } else {
if entry.LowValue != nil {
if vnut, ok := entry.LowValue.(low.HasValueNodeUntyped); ok {
vn := vnut.GetValueNode()
if vn.Kind == yaml.SequenceNode {
for i := range vn.Content {
rawNode.Content[i].Style = vn.Content[i].Style
}
}
}
}
valueNode = &rawNode valueNode = &rawNode
} }
@@ -524,18 +428,29 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
return parent return parent
case reflect.Ptr: case reflect.Ptr:
if r, ok := value.(Renderable); ok { if m, ok := value.(orderedmap.MapToYamlNoder); ok {
l := entry.LowValue
if l == nil {
if gl, ok := value.(GoesLowUntyped); ok && gl.GoLowUntyped() != nil {
l = gl.GoLowUntyped()
}
}
p := m.ToYamlNode(n, l)
if len(p.Content) > 0 {
valueNode = p
}
} else if r, ok := value.(Renderable); ok {
if gl, lg := value.(GoesLowUntyped); lg { if gl, lg := value.(GoesLowUntyped); lg {
if gl.GoLowUntyped() != nil { lut := gl.GoLowUntyped()
ut := reflect.ValueOf(gl.GoLowUntyped()) if lut != nil {
lr := lut.(low.IsReferenced)
ut := reflect.ValueOf(lr)
if !ut.IsNil() { if !ut.IsNil() {
if gl.GoLowUntyped().(low.IsReferenced).IsReference() { if lut.(low.IsReferenced).IsReference() {
if !n.Resolve { if !n.Resolve {
// TODO: use renderReference here. valueNode = n.renderReference(lut.(low.IsReferenced))
rvn := utils.CreateEmptyMapNode()
rvn.Content = append(rvn.Content, utils.CreateStringNode("$ref"))
rvn.Content = append(rvn.Content, utils.CreateStringNode(gl.GoLowUntyped().(low.IsReferenced).GetReference()))
valueNode = rvn
break break
} }
} }
@@ -621,64 +536,6 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
return parent return parent
} }
func (n *NodeBuilder) extractLowMapKeysWrapped(iu reflect.Value, x string, orderedCollection []*NodeEntry, g int) []*NodeEntry {
for _, ky := range iu.MapKeys() {
ty := ky.Interface()
if ere, eok := ty.(low.HasKeyNode); eok {
er := ere.GetKeyNode().Value
if er == x {
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: ky.Interface().(low.HasKeyNode).GetKeyNode().Line,
Value: iu.MapIndex(ky).Interface(),
})
}
} else {
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: iu.MapIndex(ky).Interface(),
})
}
}
return orderedCollection
}
func (n *NodeBuilder) extractLowMapKeys(fg reflect.Value, x string, found bool, orderedCollection []*NodeEntry, m reflect.Value, k reflect.Value) (bool, []*NodeEntry) {
if fg.IsValid() && !fg.IsZero() {
for j, ky := range fg.MapKeys() {
hu := ky.Interface()
if we, wok := hu.(low.HasKeyNode); wok {
er := we.GetKeyNode().Value
if er == x {
found = true
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: we.GetKeyNode().Line,
Value: m.MapIndex(k).Interface(),
})
}
} else {
uu := ky.Interface()
if uu == x {
// this is a map, without any low level details available
found = true
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: uu.(string),
Key: uu.(string),
Line: 9999 + j,
Value: m.MapIndex(k).Interface(),
})
}
}
}
}
return found, orderedCollection
}
// Renderable is an interface that can be implemented by types that provide a custom MarshalYAML method. // Renderable is an interface that can be implemented by types that provide a custom MarshalYAML method.
type Renderable interface { type Renderable interface {
MarshalYAML() (interface{}, error) MarshalYAML() (interface{}, error)

View File

@@ -4,82 +4,57 @@
package high package high
import ( import (
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel/high/nodes"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"reflect"
"strings"
"testing"
) )
type key struct { type valueReferenceStruct struct {
Name string `yaml:"name,omitempty"`
ref bool ref bool
refStr string refStr string
ln int Value string `yaml:"value,omitempty"`
nilval bool
v any
kn *yaml.Node
low.IsReferenced `yaml:"-"`
} }
func (k key) GetKeyNode() *yaml.Node { func (r valueReferenceStruct) IsReference() bool {
if k.kn != nil { return r.ref
return k.kn
}
kn := utils.CreateStringNode("meddy")
kn.Line = k.ln
return kn
} }
func (k key) GetValueUntyped() any { func (r valueReferenceStruct) GetReference() string {
return k.v return r.refStr
} }
func (k key) GetValueNode() *yaml.Node { func (r *valueReferenceStruct) SetReference(ref string, _ *yaml.Node) {
return k.GetValueNodeUntyped() r.refStr = ref
} }
func (k key) GetValueNodeUntyped() *yaml.Node { func (r *valueReferenceStruct) GetReferenceNode() *yaml.Node {
if k.nilval {
return nil return nil
}
kn := utils.CreateStringNode("maddy")
kn.Line = k.ln
return kn
} }
func (k key) IsReference() bool { func (r valueReferenceStruct) MarshalYAML() (interface{}, error) {
return k.ref
}
func (k key) GetReference() string {
return k.refStr
}
func (k key) SetReference(ref string) {
k.refStr = ref
}
func (k key) GoLowUntyped() any {
return &k
}
func (k key) MarshalYAML() (interface{}, error) {
return utils.CreateStringNode("pizza"), nil return utils.CreateStringNode("pizza"), nil
} }
func (k key) MarshalYAMLInline() (interface{}, error) { func (r valueReferenceStruct) MarshalYAMLInline() (interface{}, error) {
return utils.CreateStringNode("pizza-inline!"), nil return utils.CreateStringNode("pizza-inline!"), nil
} }
func (r valueReferenceStruct) GoLowUntyped() any {
return &r
}
type plug struct { type plug struct {
Name []string `yaml:"name,omitempty"` Name []string `yaml:"name,omitempty"`
} }
type test1 struct { type test1 struct {
Thrig map[string]*plug `yaml:"thrig,omitempty"` Thrig *orderedmap.Map[string, *plug] `yaml:"thrig,omitempty"`
Thing string `yaml:"thing,omitempty"` Thing string `yaml:"thing,omitempty"`
Thong int `yaml:"thong,omitempty"` Thong int `yaml:"thong,omitempty"`
Thrum int64 `yaml:"thrum,omitempty"` Thrum int64 `yaml:"thrum,omitempty"`
@@ -93,80 +68,83 @@ type test1 struct {
Throo *float64 `yaml:"throo,renderZero,omitempty"` Throo *float64 `yaml:"throo,renderZero,omitempty"`
Tharg []string `yaml:"tharg,omitempty"` Tharg []string `yaml:"tharg,omitempty"`
Type []string `yaml:"type,omitempty"` Type []string `yaml:"type,omitempty"`
Throg []*key `yaml:"throg,omitempty"` Throg []*valueReferenceStruct `yaml:"throg,omitempty"`
Thrat []interface{} `yaml:"thrat,omitempty"` Thrat []interface{} `yaml:"thrat,omitempty"`
Thrag []map[string][]string `yaml:"thrag,omitempty"` Thrag []*orderedmap.Map[string, []string] `yaml:"thrag,omitempty"`
Thrug map[string]string `yaml:"thrug,omitempty"` Thrug *orderedmap.Map[string, string] `yaml:"thrug,omitempty"`
Thoom []map[string]string `yaml:"thoom,omitempty"` Thoom []*orderedmap.Map[string, string] `yaml:"thoom,omitempty"`
Thomp map[key]string `yaml:"thomp,omitempty"` Thomp *orderedmap.Map[low.KeyReference[string], string] `yaml:"thomp,omitempty"`
Thump key `yaml:"thump,omitempty"` Thump valueReferenceStruct `yaml:"thump,omitempty"`
Thane key `yaml:"thane,omitempty"` Thane valueReferenceStruct `yaml:"thane,omitempty"`
Thunk key `yaml:"thunk,omitempty"` Thunk valueReferenceStruct `yaml:"thunk,omitempty"`
Thrim *key `yaml:"thrim,omitempty"` Thrim *valueReferenceStruct `yaml:"thrim,omitempty"`
Thril map[string]*key `yaml:"thril,omitempty"` Thril *orderedmap.Map[string, *valueReferenceStruct] `yaml:"thril,omitempty"`
Extensions map[string]any `yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `yaml:"-"`
ignoreMe string `yaml:"-"` ignoreMe string `yaml:"-"`
IgnoreMe string `yaml:"-"` IgnoreMe string `yaml:"-"`
} }
func (te *test1) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (te *test1) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
g := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
g := make(map[low.KeyReference[string]]low.ValueReference[any]) i := 0
for pair := orderedmap.First(te.Extensions); pair != nil; pair = pair.Next() {
kn := utils.CreateStringNode(pair.Key())
kn.Line = 999999 + i // weighted to the bottom.
for i := range te.Extensions { g.Set(low.KeyReference[string]{
Value: pair.Key(),
f := reflect.TypeOf(te.Extensions[i])
switch f.Kind() {
case reflect.String:
vn := utils.CreateStringNode(te.Extensions[i].(string))
vn.Line = 999999 // weighted to the bottom.
g[low.KeyReference[string]{
Value: i,
KeyNode: vn,
}] = low.ValueReference[any]{
ValueNode: vn,
Value: te.Extensions[i].(string),
}
case reflect.Map:
kn := utils.CreateStringNode(i)
var vn yaml.Node
_ = vn.Decode(te.Extensions[i])
kn.Line = 999999 // weighted to the bottom.
g[low.KeyReference[string]{
Value: i,
KeyNode: kn, KeyNode: kn,
}] = low.ValueReference[any]{ }, low.ValueReference[*yaml.Node]{
ValueNode: &vn, ValueNode: pair.Value(),
Value: te.Extensions[i], Value: pair.Value(),
} })
} i++
} }
return g return g
} }
func (te *test1) MarshalYAML() (interface{}, error) { func (te *test1) MarshalYAML() (interface{}, error) {
panic("MarshalYAML")
nb := NewNodeBuilder(te, te) nb := NewNodeBuilder(te, te)
return nb.Render(), nil return nb.Render(), nil
} }
func (te *test1) GetKeyNode() *yaml.Node { func (te *test1) GetKeyNode() *yaml.Node {
panic("GetKeyNode")
kn := utils.CreateStringNode("meddy") kn := utils.CreateStringNode("meddy")
kn.Line = 20 kn.Line = 20
return kn return kn
} }
func (te *test1) GoesLowUntyped() any { func (te *test1) GoesLowUntyped() any {
panic("GoesLowUntyped")
return te return te
} }
func TestNewNodeBuilder(t *testing.T) { func TestNewNodeBuilder(t *testing.T) {
b := true b := true
c := int64(12345) c := int64(12345)
d := 1234.1234 d := 1234.1234
thoom1 := orderedmap.New[string, string]()
thoom1.Set("maddy", "champion")
thoom2 := orderedmap.New[string, string]()
thoom2.Set("ember", "naughty")
thomp := orderedmap.New[low.KeyReference[string], string]()
thomp.Set(low.KeyReference[string]{
Value: "meddy",
KeyNode: utils.CreateStringNode("meddy"),
}, "princess")
thrug := orderedmap.New[string, string]()
thrug.Set("chicken", "nuggets")
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("time"))
t1 := test1{ t1 := test1{
ignoreMe: "I should never be seen!", ignoreMe: "I should never be seen!",
Thing: "ding", Thing: "ding",
@@ -181,30 +159,18 @@ func TestNewNodeBuilder(t *testing.T) {
Thral: &d, Thral: &d,
Tharg: []string{"chicken", "nuggets"}, Tharg: []string{"chicken", "nuggets"},
Type: []string{"chicken"}, Type: []string{"chicken"},
Thoom: []map[string]string{ Thoom: []*orderedmap.Map[string, string]{
{ thoom1,
"maddy": "champion", thoom2,
}, },
{ Thomp: thomp,
"ember": "naughty", Thane: valueReferenceStruct{ // this is going to be ignored, needs to be a ValueReference
}, Value: "ripples",
},
Thomp: map[key]string{
{ln: 1}: "princess",
},
Thane: key{ // this is going to be ignored, needs to be a ValueReference
ln: 2,
ref: true,
refStr: "ripples",
},
Thrug: map[string]string{
"chicken": "nuggets",
},
Thump: key{Name: "I will be ignored", ln: 3},
Thunk: key{ln: 4, nilval: true},
Extensions: map[string]any{
"x-pizza": "time",
}, },
Thrug: thrug,
Thump: valueReferenceStruct{Value: "I will be ignored"},
Thunk: valueReferenceStruct{},
Extensions: ext,
} }
nb := NewNodeBuilder(&t1, nil) nb := NewNodeBuilder(&t1, nil)
@@ -235,11 +201,9 @@ thomp:
x-pizza: time` x-pizza: time`
assert.Equal(t, desired, strings.TrimSpace(string(data))) assert.Equal(t, desired, strings.TrimSpace(string(data)))
} }
func TestNewNodeBuilder_Type(t *testing.T) { func TestNewNodeBuilder_Type(t *testing.T) {
t1 := test1{ t1 := test1{
Type: []string{"chicken", "soup"}, Type: []string{"chicken", "soup"},
} }
@@ -257,15 +221,12 @@ func TestNewNodeBuilder_Type(t *testing.T) {
} }
func TestNewNodeBuilder_IsReferenced(t *testing.T) { func TestNewNodeBuilder_IsReferenced(t *testing.T) {
t1 := &low.ValueReference[string]{
t1 := key{ Value: "cotton",
Name: "cotton",
ref: true,
refStr: "#/my/heart",
ln: 2,
} }
t1.SetReference("#/my/heart", nil)
nb := NewNodeBuilder(&t1, &t1) nb := NewNodeBuilder(t1, t1)
node := nb.Render() node := nb.Render()
data, _ := yaml.Marshal(node) data, _ := yaml.Marshal(node)
@@ -276,13 +237,13 @@ func TestNewNodeBuilder_IsReferenced(t *testing.T) {
} }
func TestNewNodeBuilder_Extensions(t *testing.T) { func TestNewNodeBuilder_Extensions(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("time"))
ext.Set("x-money", utils.CreateStringNode("time"))
t1 := test1{ t1 := test1{
Thing: "ding", Thing: "ding",
Extensions: map[string]any{ Extensions: ext,
"x-pizza": "time",
"x-money": "time",
},
Thong: 1, Thong: 1,
} }
@@ -294,13 +255,13 @@ func TestNewNodeBuilder_Extensions(t *testing.T) {
} }
func TestNewNodeBuilder_LowValueNode(t *testing.T) { func TestNewNodeBuilder_LowValueNode(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("time"))
ext.Set("x-money", utils.CreateStringNode("time"))
t1 := test1{ t1 := test1{
Thing: "ding", Thing: "ding",
Extensions: map[string]any{ Extensions: ext,
"x-pizza": "time",
"x-money": "time",
},
Thong: 1, Thong: 1,
} }
@@ -313,12 +274,11 @@ func TestNewNodeBuilder_LowValueNode(t *testing.T) {
} }
func TestNewNodeBuilder_NoValue(t *testing.T) { func TestNewNodeBuilder_NoValue(t *testing.T) {
t1 := test1{ t1 := test1{
Thing: "", Thing: "",
} }
nodeEnty := NodeEntry{} nodeEnty := nodes.NodeEntry{}
nb := NewNodeBuilder(&t1, &t1) nb := NewNodeBuilder(&t1, &t1)
node := nb.AddYAMLNode(nil, &nodeEnty) node := nb.AddYAMLNode(nil, &nodeEnty)
assert.Nil(t, node) assert.Nil(t, node)
@@ -326,7 +286,7 @@ func TestNewNodeBuilder_NoValue(t *testing.T) {
func TestNewNodeBuilder_EmptyString(t *testing.T) { func TestNewNodeBuilder_EmptyString(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nodeEnty := NodeEntry{} nodeEnty := nodes.NodeEntry{}
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
node := nb.AddYAMLNode(nil, &nodeEnty) node := nb.AddYAMLNode(nil, &nodeEnty)
assert.Nil(t, node) assert.Nil(t, node)
@@ -334,7 +294,7 @@ func TestNewNodeBuilder_EmptyString(t *testing.T) {
func TestNewNodeBuilder_EmptyStringRenderZero(t *testing.T) { func TestNewNodeBuilder_EmptyStringRenderZero(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nodeEnty := NodeEntry{RenderZero: true, Value: ""} nodeEnty := nodes.NodeEntry{RenderZero: true, Value: ""}
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
m := utils.CreateEmptyMapNode() m := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(m, &nodeEnty) node := nb.AddYAMLNode(m, &nodeEnty)
@@ -344,7 +304,7 @@ func TestNewNodeBuilder_EmptyStringRenderZero(t *testing.T) {
func TestNewNodeBuilder_Bool(t *testing.T) { func TestNewNodeBuilder_Bool(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
nodeEnty := NodeEntry{} nodeEnty := nodes.NodeEntry{}
node := nb.AddYAMLNode(nil, &nodeEnty) node := nb.AddYAMLNode(nil, &nodeEnty)
assert.Nil(t, node) assert.Nil(t, node)
} }
@@ -364,7 +324,7 @@ func TestNewNodeBuilder_Int(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode() p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: 12, Key: "p"} nodeEnty := nodes.NodeEntry{Tag: "p", Value: 12, Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty) node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node) assert.NotNil(t, node)
assert.Len(t, node.Content, 2) assert.Len(t, node.Content, 2)
@@ -375,7 +335,7 @@ func TestNewNodeBuilder_Int64(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode() p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: int64(234556), Key: "p"} nodeEnty := nodes.NodeEntry{Tag: "p", Value: int64(234556), Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty) node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node) assert.NotNil(t, node)
assert.Len(t, node.Content, 2) assert.Len(t, node.Content, 2)
@@ -386,7 +346,7 @@ func TestNewNodeBuilder_Float32(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode() p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: float32(1234.23), Key: "p"} nodeEnty := nodes.NodeEntry{Tag: "p", Value: float32(1234.23), Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty) node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node) assert.NotNil(t, node)
assert.Len(t, node.Content, 2) assert.Len(t, node.Content, 2)
@@ -397,7 +357,7 @@ func TestNewNodeBuilder_Float64(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode() p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: 1234.232323, Key: "p", StringValue: "1234.232323"} nodeEnty := nodes.NodeEntry{Tag: "p", Value: 1234.232323, Key: "p", StringValue: "1234.232323"}
node := nb.AddYAMLNode(p, &nodeEnty) node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node) assert.NotNil(t, node)
assert.Len(t, node.Content, 2) assert.Len(t, node.Content, 2)
@@ -410,30 +370,33 @@ func TestNewNodeBuilder_EmptyNode(t *testing.T) {
nb.Nodes = nil nb.Nodes = nil
m := nb.Render() m := nb.Render()
assert.Len(t, m.Content, 0) assert.Len(t, m.Content, 0)
} }
func TestNewNodeBuilder_MapKeyHasValue(t *testing.T) { func TestNewNodeBuilder_MapKeyHasValue(t *testing.T) {
thrug := orderedmap.New[string, string]()
thrug.Set("dump", "trump")
t1 := test1{ t1 := test1{
Thrug: map[string]string{ Thrug: thrug,
"dump": "trump",
},
} }
type test1low struct { type test1low struct {
Thrug key `yaml:"thrug"` Thrug *orderedmap.Map[*low.KeyReference[string], *low.ValueReference[string]] `yaml:"thrug"`
Thugg *bool `yaml:"thugg"` Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"` Throo *float32 `yaml:"throo"`
} }
thrugLow := orderedmap.New[*low.KeyReference[string], *low.ValueReference[string]]()
thrugLow.Set(&low.KeyReference[string]{
Value: "dump",
KeyNode: utils.CreateStringNode("dump"),
}, &low.ValueReference[string]{
Value: "trump",
ValueNode: utils.CreateStringNode("trump"),
})
t2 := test1low{ t2 := test1low{
Thrug: key{ Thrug: thrugLow,
v: map[string]string{
"dump": "trump",
},
ln: 2,
},
} }
nb := NewNodeBuilder(&t1, &t2) nb := NewNodeBuilder(&t1, &t2)
@@ -448,31 +411,30 @@ func TestNewNodeBuilder_MapKeyHasValue(t *testing.T) {
} }
func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) { func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) {
thomp := orderedmap.New[low.KeyReference[string], string]()
thomp.Set(low.KeyReference[string]{Value: "meddy", KeyNode: utils.CreateStringNode("meddy")}, "princess")
t1 := test1{ t1 := test1{
Thomp: map[key]string{ Thomp: thomp,
{v: "who"}: "princess",
},
} }
type test1low struct { type test1low struct {
Thomp key `yaml:"thomp"` Thomp low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"` Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"` Throo *float32 `yaml:"throo"`
} }
valueMap := orderedmap.New[low.KeyReference[string], low.ValueReference[string]]()
valueMap.Set(low.KeyReference[string]{
Value: "ice",
KeyNode: utils.CreateStringNode("ice"),
}, low.ValueReference[string]{
Value: "princess",
})
t2 := test1low{ t2 := test1low{
Thomp: key{ Thomp: low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]]{
v: map[key]string{ Value: valueMap,
{
v: key{
v: "ice",
kn: utils.CreateStringNode("limes"),
},
kn: utils.CreateStringNode("chimes"),
ln: 6}: "princess",
},
ln: 2,
}, },
} }
@@ -488,23 +450,27 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) {
} }
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) { func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) {
thomp := orderedmap.New[low.KeyReference[string], string]()
thomp.Set(low.KeyReference[string]{Value: "meddy", KeyNode: utils.CreateStringNode("meddy")}, "princess")
t1 := test1{ t1 := test1{
Thomp: map[key]string{ Thomp: thomp,
{v: "who"}: "princess",
},
} }
type test1low struct { type test1low struct {
Thomp low.NodeReference[map[key]string] `yaml:"thomp"` Thomp low.NodeReference[*orderedmap.Map[low.KeyReference[string], string]] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"` Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"` Throo *float32 `yaml:"throo"`
} }
g := low.NodeReference[map[key]string]{ valMap := orderedmap.New[low.KeyReference[string], string]()
Value: map[key]string{ valMap.Set(low.KeyReference[string]{
{v: "my", kn: utils.CreateStringNode("limes")}: "princess", Value: "meddy",
}, KeyNode: utils.CreateStringNode("meddy"),
}, "princess")
g := low.NodeReference[*orderedmap.Map[low.KeyReference[string], string]]{
Value: valMap,
} }
t2 := test1low{ t2 := test1low{
@@ -522,93 +488,26 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data))) assert.Equal(t, desired, strings.TrimSpace(string(data)))
} }
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatchKeyNode(t *testing.T) {
t1 := test1{
Thomp: map[key]string{
{v: "who"}: "princess",
},
}
type test1low struct {
Thomp low.NodeReference[map[key]string] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
g := low.NodeReference[map[key]string]{
Value: map[key]string{
{v: "my", kn: utils.CreateStringNode("limes")}: "princess",
},
}
t2 := test1low{
Thomp: g,
}
nb := NewNodeBuilder(&t1, &t2)
node := nb.Render()
data, _ := yaml.Marshal(node)
desired := `thomp:
meddy: princess`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch_NoWrap(t *testing.T) {
t1 := test1{
Thomp: map[key]string{
{v: "who"}: "princess",
},
}
type test1low struct {
Thomp map[key]string `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
t2 := test1low{
Thomp: map[key]string{
{v: "my", kn: utils.CreateStringNode("meddy")}: "princess",
},
}
nb := NewNodeBuilder(&t1, &t2)
node := nb.Render()
data, _ := yaml.Marshal(node)
desired := `thomp:
meddy: princess`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_MissingLabel(t *testing.T) { func TestNewNodeBuilder_MissingLabel(t *testing.T) {
t1 := new(test1) t1 := new(test1)
nb := NewNodeBuilder(t1, t1) nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode() p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Value: 1234.232323, Key: "p"} nodeEnty := nodes.NodeEntry{Value: 1234.232323, Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty) node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node) assert.NotNil(t, node)
assert.Len(t, node.Content, 0) assert.Len(t, node.Content, 0)
} }
func TestNewNodeBuilder_ExtensionMap(t *testing.T) { func TestNewNodeBuilder_ExtensionMap(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
pizza := orderedmap.New[string, string]()
pizza.Set("dump", "trump")
ext.Set("x-pizza", utils.CreateYamlNode(pizza))
ext.Set("x-money", utils.CreateStringNode("time"))
t1 := test1{ t1 := test1{
Thing: "ding", Thing: "ding",
Extensions: map[string]any{ Extensions: ext,
"x-pizza": map[string]string{
"dump": "trump",
},
"x-money": "time",
},
Thong: 1, Thong: 1,
} }
@@ -621,20 +520,21 @@ func TestNewNodeBuilder_ExtensionMap(t *testing.T) {
} }
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) { func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
pizza := orderedmap.New[string, string]()
pizza.Set("dump", "trump")
ext.Set("x-pizza", utils.CreateYamlNode(pizza))
cake := orderedmap.New[string, string]()
cake.Set("maga", "nomore")
ext.Set("x-cake", utils.CreateYamlNode(cake))
thril := orderedmap.New[string, *valueReferenceStruct]()
thril.Set("princess", &valueReferenceStruct{Value: "who"})
thril.Set("heavy", &valueReferenceStruct{Value: "who"})
t1 := test1{ t1 := test1{
Extensions: map[string]any{ Extensions: ext,
"x-pizza": map[string]string{ Thril: thril,
"dump": "trump",
},
"x-cake": map[string]string{
"maga": "nomore",
},
},
Thril: map[string]*key{
"princess": {v: "who", Name: "beef", ln: 2},
"heavy": {v: "who", Name: "industries", ln: 3},
},
} }
nb := NewNodeBuilder(&t1, nil) nb := NewNodeBuilder(&t1, nil)
@@ -642,13 +542,18 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) {
data, _ := yaml.Marshal(node) data, _ := yaml.Marshal(node)
assert.Len(t, data, 94) assert.Equal(t, `thril:
princess: pizza
heavy: pizza
x-pizza:
dump: trump
x-cake:
maga: nomore`, strings.TrimSpace(string(data)))
} }
func TestNewNodeBuilder_SliceRef(t *testing.T) { func TestNewNodeBuilder_SliceRef(t *testing.T) {
c := valueReferenceStruct{ref: true, refStr: "#/red/robin/yummmmm", Value: "milky"}
c := key{ref: true, refStr: "#/red/robin/yummmmm", Name: "milky"} ty := []*valueReferenceStruct{&c}
ty := []*key{&c}
t1 := test1{ t1 := test1{
Throg: ty, Throg: ty,
} }
@@ -665,9 +570,8 @@ func TestNewNodeBuilder_SliceRef(t *testing.T) {
} }
func TestNewNodeBuilder_SliceRef_Inline(t *testing.T) { func TestNewNodeBuilder_SliceRef_Inline(t *testing.T) {
c := valueReferenceStruct{Value: "milky"}
c := key{ref: true, refStr: "#/red/robin/yummmmm", Name: "milky"} ty := []*valueReferenceStruct{&c}
ty := []*key{&c}
t1 := test1{ t1 := test1{
Throg: ty, Throg: ty,
} }
@@ -684,22 +588,19 @@ func TestNewNodeBuilder_SliceRef_Inline(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data))) assert.Equal(t, desired, strings.TrimSpace(string(data)))
} }
type testRender struct { type testRender struct{}
}
func (t testRender) MarshalYAML() (interface{}, error) { func (t testRender) MarshalYAML() (interface{}, error) {
return utils.CreateStringNode("testy!"), nil return utils.CreateStringNode("testy!"), nil
} }
type testRenderRawNode struct { type testRenderRawNode struct{}
}
func (t testRenderRawNode) MarshalYAML() (interface{}, error) { func (t testRenderRawNode) MarshalYAML() (interface{}, error) {
return yaml.Node{Kind: yaml.ScalarNode, Value: "zesty!"}, nil return yaml.Node{Kind: yaml.ScalarNode, Value: "zesty!"}, nil
} }
func TestNewNodeBuilder_SliceRef_Inline_NotCompatible(t *testing.T) { func TestNewNodeBuilder_SliceRef_Inline_NotCompatible(t *testing.T) {
ty := []interface{}{testRender{}} ty := []interface{}{testRender{}}
t1 := test1{ t1 := test1{
Thrat: ty, Thrat: ty,
@@ -718,7 +619,6 @@ func TestNewNodeBuilder_SliceRef_Inline_NotCompatible(t *testing.T) {
} }
func TestNewNodeBuilder_SliceRef_Inline_NotCompatible_NotPointer(t *testing.T) { func TestNewNodeBuilder_SliceRef_Inline_NotCompatible_NotPointer(t *testing.T) {
ty := []interface{}{testRenderRawNode{}} ty := []interface{}{testRenderRawNode{}}
t1 := test1{ t1 := test1{
Thrat: ty, Thrat: ty,
@@ -737,7 +637,6 @@ func TestNewNodeBuilder_SliceRef_Inline_NotCompatible_NotPointer(t *testing.T) {
} }
func TestNewNodeBuilder_PointerRef_Inline_NotCompatible_RawNode(t *testing.T) { func TestNewNodeBuilder_PointerRef_Inline_NotCompatible_RawNode(t *testing.T) {
ty := testRenderRawNode{} ty := testRenderRawNode{}
t1 := test1{ t1 := test1{
Thurm: &ty, Thurm: &ty,
@@ -755,8 +654,7 @@ func TestNewNodeBuilder_PointerRef_Inline_NotCompatible_RawNode(t *testing.T) {
} }
func TestNewNodeBuilder_PointerRef_Inline_NotCompatible(t *testing.T) { func TestNewNodeBuilder_PointerRef_Inline_NotCompatible(t *testing.T) {
ty := valueReferenceStruct{}
ty := key{}
t1 := test1{ t1 := test1{
Thurm: &ty, Thurm: &ty,
} }
@@ -773,9 +671,8 @@ func TestNewNodeBuilder_PointerRef_Inline_NotCompatible(t *testing.T) {
} }
func TestNewNodeBuilder_SliceNoRef(t *testing.T) { func TestNewNodeBuilder_SliceNoRef(t *testing.T) {
c := valueReferenceStruct{Value: "milky"}
c := key{ref: false, Name: "milky"} ty := []*valueReferenceStruct{&c}
ty := []*key{&c}
t1 := test1{ t1 := test1{
Throg: ty, Throg: ty,
} }
@@ -792,7 +689,6 @@ func TestNewNodeBuilder_SliceNoRef(t *testing.T) {
} }
func TestNewNodeBuilder_TestStructAny(t *testing.T) { func TestNewNodeBuilder_TestStructAny(t *testing.T) {
t1 := test1{ t1 := test1{
Thurm: low.ValueReference[any]{ Thurm: low.ValueReference[any]{
ValueNode: utils.CreateStringNode("beer"), ValueNode: utils.CreateStringNode("beer"),
@@ -808,8 +704,8 @@ func TestNewNodeBuilder_TestStructAny(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data))) assert.Equal(t, desired, strings.TrimSpace(string(data)))
} }
func TestNewNodeBuilder_TestStructString(t *testing.T) {
func TestNewNodeBuilder_TestStructString(t *testing.T) {
t1 := test1{ t1 := test1{
Thurm: low.ValueReference[string]{ Thurm: low.ValueReference[string]{
ValueNode: utils.CreateStringNode("beer"), ValueNode: utils.CreateStringNode("beer"),
@@ -827,12 +723,11 @@ func TestNewNodeBuilder_TestStructString(t *testing.T) {
} }
func TestNewNodeBuilder_TestStructPointer(t *testing.T) { func TestNewNodeBuilder_TestStructPointer(t *testing.T) {
t1 := test1{ t1 := test1{
Thrim: &key{ Thrim: &valueReferenceStruct{
ref: true, ref: true,
refStr: "#/cash/money", refStr: "#/cash/money",
Name: "pizza", Value: "pizza",
}, },
} }
@@ -848,17 +743,17 @@ func TestNewNodeBuilder_TestStructPointer(t *testing.T) {
} }
func TestNewNodeBuilder_TestStructRef(t *testing.T) { func TestNewNodeBuilder_TestStructRef(t *testing.T) {
fkn := utils.CreateStringNode("pizzaBurgers") fkn := utils.CreateStringNode("pizzaBurgers")
fkn.Line = 22 fkn.Line = 22
t1 := test1{ thurm := low.NodeReference[string]{
Thurm: low.NodeReference[string]{
Reference: "#/cash/money",
ReferenceNode: true,
KeyNode: fkn, KeyNode: fkn,
ValueNode: fkn, ValueNode: fkn,
}, }
thurm.SetReference("#/cash/money", nil)
t1 := test1{
Thurm: thurm,
} }
nb := NewNodeBuilder(&t1, &t1) nb := NewNodeBuilder(&t1, &t1)
@@ -872,7 +767,6 @@ func TestNewNodeBuilder_TestStructRef(t *testing.T) {
} }
func TestNewNodeBuilder_TestStructDefaultEncode(t *testing.T) { func TestNewNodeBuilder_TestStructDefaultEncode(t *testing.T) {
f := 1 f := 1
t1 := test1{ t1 := test1{
Thurm: &f, Thurm: &f,
@@ -889,9 +783,10 @@ func TestNewNodeBuilder_TestStructDefaultEncode(t *testing.T) {
} }
func TestNewNodeBuilder_TestSliceMapSliceStruct(t *testing.T) { func TestNewNodeBuilder_TestSliceMapSliceStruct(t *testing.T) {
pizza := orderedmap.New[string, []string]()
a := []map[string][]string{ pizza.Set("pizza", []string{"beer", "wine"})
{"pizza": {"beer", "wine"}}, a := []*orderedmap.Map[string, []string]{
pizza,
} }
t1 := test1{ t1 := test1{
@@ -912,7 +807,6 @@ func TestNewNodeBuilder_TestSliceMapSliceStruct(t *testing.T) {
} }
func TestNewNodeBuilder_TestRenderZero(t *testing.T) { func TestNewNodeBuilder_TestRenderZero(t *testing.T) {
f := false f := false
t1 := test1{ t1 := test1{
Thugg: &f, Thugg: &f,
@@ -929,7 +823,6 @@ func TestNewNodeBuilder_TestRenderZero(t *testing.T) {
} }
func TestNewNodeBuilder_TestRenderZero_Float(t *testing.T) { func TestNewNodeBuilder_TestRenderZero_Float(t *testing.T) {
f := 0.0 f := 0.0
t1 := test1{ t1 := test1{
Throo: &f, Throo: &f,
@@ -946,7 +839,6 @@ func TestNewNodeBuilder_TestRenderZero_Float(t *testing.T) {
} }
func TestNewNodeBuilder_TestRenderZero_Float_NotZero(t *testing.T) { func TestNewNodeBuilder_TestRenderZero_Float_NotZero(t *testing.T) {
f := 0.12 f := 0.12
t1 := test1{ t1 := test1{
Throo: &f, Throo: &f,
@@ -963,11 +855,11 @@ func TestNewNodeBuilder_TestRenderZero_Float_NotZero(t *testing.T) {
} }
func TestNewNodeBuilder_TestRenderServerVariableSimulation(t *testing.T) { func TestNewNodeBuilder_TestRenderServerVariableSimulation(t *testing.T) {
thrig := orderedmap.New[string, *plug]()
thrig.Set("pork", &plug{Name: []string{"gammon", "bacon"}})
t1 := test1{ t1 := test1{
Thrig: map[string]*plug{ Thrig: thrig,
"pork": {Name: []string{"gammon", "bacon"}},
},
} }
nb := NewNodeBuilder(&t1, &t1) nb := NewNodeBuilder(&t1, &t1)
@@ -985,22 +877,21 @@ func TestNewNodeBuilder_TestRenderServerVariableSimulation(t *testing.T) {
} }
func TestNewNodeBuilder_ShouldHaveNotDoneTestsLikeThisOhWell(t *testing.T) { func TestNewNodeBuilder_ShouldHaveNotDoneTestsLikeThisOhWell(t *testing.T) {
m := orderedmap.New[low.KeyReference[string], low.ValueReference[*valueReferenceStruct]]()
m := make(map[low.KeyReference[string]]low.ValueReference[*key]) m.Set(low.KeyReference[string]{
m[low.KeyReference[string]{
KeyNode: utils.CreateStringNode("pizza"), KeyNode: utils.CreateStringNode("pizza"),
Value: "pizza", Value: "pizza",
}] = low.ValueReference[*key]{ }, low.ValueReference[*valueReferenceStruct]{
ValueNode: utils.CreateStringNode("beer"), ValueNode: utils.CreateStringNode("beer"),
Value: &key{}, Value: &valueReferenceStruct{},
} })
d := make(map[string]*key) d := orderedmap.New[string, *valueReferenceStruct]()
d["pizza"] = &key{} d.Set("pizza", &valueReferenceStruct{})
type t1low struct { type t1low struct {
Thril low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*key]] Thril low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*valueReferenceStruct]]]
Thugg *bool `yaml:"thugg"` Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"` Throo *float32 `yaml:"throo"`
} }
@@ -1010,7 +901,7 @@ func TestNewNodeBuilder_ShouldHaveNotDoneTestsLikeThisOhWell(t *testing.T) {
} }
t2 := t1low{ t2 := t1low{
Thril: low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*key]]{ Thril: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*valueReferenceStruct]]]{
Value: m, Value: m,
ValueNode: utils.CreateStringNode("beer"), ValueNode: utils.CreateStringNode("beer"),
}, },

View File

@@ -0,0 +1,16 @@
package nodes
import "gopkg.in/yaml.v3"
// NodeEntry represents a single node used by NodeBuilder.
type NodeEntry struct {
Tag string
Key string
Value any
StringValue string
Line int
KeyStyle yaml.Style
// ValueStyle yaml.Style
RenderZero bool
LowValue any
}

View File

@@ -15,12 +15,13 @@ package high
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// GoesLow is used to represent any high-level model. All high level models meet this interface and can be used to // GoesLow is used to represent any high-level model. All high level models meet this interface and can be used to
// extract low-level models from any high-level model. // extract low-level models from any high-level model.
type GoesLow[T any] interface { type GoesLow[T any] interface {
// GoLow returns the low-level object that was used to create the high-level object. This allows consumers // GoLow returns the low-level object that was used to create the high-level object. This allows consumers
// to dive-down into the plumbing API at any point in the model. // to dive-down into the plumbing API at any point in the model.
GoLow() T GoLow() T
@@ -29,18 +30,17 @@ type GoesLow[T any] interface {
// GoesLowUntyped is used to represent any high-level model. All high level models meet this interface and can be used to // GoesLowUntyped is used to represent any high-level model. All high level models meet this interface and can be used to
// extract low-level models from any high-level model. // extract low-level models from any high-level model.
type GoesLowUntyped interface { type GoesLowUntyped interface {
// GoLowUntyped returns the low-level object that was used to create the high-level object. This allows consumers // GoLowUntyped returns the low-level object that was used to create the high-level object. This allows consumers
// to dive-down into the plumbing API at any point in the model. // to dive-down into the plumbing API at any point in the model.
GoLowUntyped() any GoLowUntyped() any
} }
// ExtractExtensions is a convenience method for converting low-level extension definitions, to a high level map[string]any // ExtractExtensions is a convenience method for converting low-level extension definitions, to a high level *orderedmap.Map[string, *yaml.Node]
// definition that is easier to consume in applications. // definition that is easier to consume in applications.
func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReference[any]) map[string]any { func ExtractExtensions(extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]) *orderedmap.Map[string, *yaml.Node] {
extracted := make(map[string]any) extracted := orderedmap.New[string, *yaml.Node]()
for k, v := range extensions { for pair := orderedmap.First(extensions); pair != nil; pair = pair.Next() {
extracted[k.Value] = v.Value extracted.Set(pair.Key().Value, pair.Value().Value)
} }
return extracted return extracted
} }
@@ -61,18 +61,18 @@ func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReferenc
// //
// schema := schemaProxy.Schema() // any high-level object that has // schema := schemaProxy.Schema() // any high-level object that has
// extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema) // extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema)
func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) { func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (*orderedmap.Map[string, *T], error) {
m := make(map[string]*T) m := orderedmap.New[string, *T]()
ext := low.GoLow().GetExtensions() ext := low.GoLow().GetExtensions()
for i := range ext { for pair := orderedmap.First(ext); pair != nil; pair = pair.Next() {
key := i.Value key := pair.Key().Value
g := new(T) g := new(T)
valueNode := ext[i].ValueNode valueNode := pair.Value().ValueNode
err := valueNode.Decode(g) err := valueNode.Decode(g)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m[key] = g m.Set(key, g)
} }
return m, nil return m, nil
} }

View File

@@ -4,21 +4,30 @@
package high package high
import ( import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing" "testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
) )
func TestExtractExtensions(t *testing.T) { func TestExtractExtensions(t *testing.T) {
n := make(map[low.KeyReference[string]]low.ValueReference[any]) n := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
n[low.KeyReference[string]{ n.Set(low.KeyReference[string]{
Value: "pb33f", Value: "pb33f",
}] = low.ValueReference[any]{ }, low.ValueReference[*yaml.Node]{
Value: "new cowboy in town", Value: utils.CreateStringNode("new cowboy in town"),
} })
ext := ExtractExtensions(n) ext := ExtractExtensions(n)
assert.Equal(t, "new cowboy in town", ext["pb33f"])
var pb33f string
err := ext.GetOrZero("pb33f").Decode(&pb33f)
require.NoError(t, err)
assert.Equal(t, "new cowboy in town", pb33f)
} }
type textExtension struct { type textExtension struct {
@@ -35,15 +44,14 @@ func (p *parent) GoLow() *child {
} }
type child struct { type child struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
func (c *child) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (c *child) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return c.Extensions return c.Extensions
} }
func TestUnpackExtensions(t *testing.T) { func TestUnpackExtensions(t *testing.T) {
var resultA, resultB yaml.Node var resultA, resultB yaml.Node
ymlA := ` ymlA := `
@@ -59,18 +67,18 @@ power: 2`
err = yaml.Unmarshal([]byte(ymlB), &resultB) err = yaml.Unmarshal([]byte(ymlB), &resultB)
assert.NoError(t, err) assert.NoError(t, err)
n := make(map[low.KeyReference[string]]low.ValueReference[any]) n := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
n[low.KeyReference[string]{ n.Set(low.KeyReference[string]{
Value: "x-rancher-a", Value: "x-rancher-a",
}] = low.ValueReference[any]{ }, low.ValueReference[*yaml.Node]{
ValueNode: resultA.Content[0], ValueNode: resultA.Content[0],
} })
n[low.KeyReference[string]{ n.Set(low.KeyReference[string]{
Value: "x-rancher-b", Value: "x-rancher-b",
}] = low.ValueReference[any]{ }, low.ValueReference[*yaml.Node]{
ValueNode: resultB.Content[0], ValueNode: resultB.Content[0],
} })
c := new(child) c := new(child)
c.Extensions = n c.Extensions = n
@@ -81,14 +89,13 @@ power: 2`
res, err := UnpackExtensions[textExtension, *child](p) res, err := UnpackExtensions[textExtension, *child](p)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, res) assert.NotEmpty(t, res)
assert.Equal(t, "buckaroo", res["x-rancher-a"].Cowboy) assert.Equal(t, "buckaroo", res.GetOrZero("x-rancher-a").Cowboy)
assert.Equal(t, 100, res["x-rancher-a"].Power) assert.Equal(t, 100, res.GetOrZero("x-rancher-a").Power)
assert.Equal(t, "frogman", res["x-rancher-b"].Cowboy) assert.Equal(t, "frogman", res.GetOrZero("x-rancher-b").Cowboy)
assert.Equal(t, 2, res["x-rancher-b"].Power) assert.Equal(t, 2, res.GetOrZero("x-rancher-b").Power)
} }
func TestUnpackExtensions_Fail(t *testing.T) { func TestUnpackExtensions_Fail(t *testing.T) {
var resultA, resultB yaml.Node var resultA, resultB yaml.Node
ymlA := ` ymlA := `
@@ -105,18 +112,18 @@ power: hello`
err = yaml.Unmarshal([]byte(ymlB), &resultB) err = yaml.Unmarshal([]byte(ymlB), &resultB)
assert.NoError(t, err) assert.NoError(t, err)
n := make(map[low.KeyReference[string]]low.ValueReference[any]) n := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
n[low.KeyReference[string]{ n.Set(low.KeyReference[string]{
Value: "x-rancher-a", Value: "x-rancher-a",
}] = low.ValueReference[any]{ }, low.ValueReference[*yaml.Node]{
ValueNode: resultA.Content[0], ValueNode: resultA.Content[0],
} })
n[low.KeyReference[string]{ n.Set(low.KeyReference[string]{
Value: "x-rancher-b", Value: "x-rancher-b",
}] = low.ValueReference[any]{ }, low.ValueReference[*yaml.Node]{
ValueNode: resultB.Content[0], ValueNode: resultB.Content[0],
} })
c := new(child) c := new(child)
c.Extensions = n c.Extensions = n

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel"
highbase "github.com/pb33f/libopenapi/datamodel/high/base" highbase "github.com/pb33f/libopenapi/datamodel/high/base"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -17,7 +18,7 @@ import (
// arrays or models. // arrays or models.
// - https://swagger.io/specification/v2/#definitionsObject // - https://swagger.io/specification/v2/#definitionsObject
type Definitions struct { type Definitions struct {
Definitions orderedmap.Map[string, *highbase.SchemaProxy] Definitions *orderedmap.Map[string, *highbase.SchemaProxy]
low *low.Definitions low *low.Definitions
} }
@@ -38,7 +39,7 @@ func NewDefinitions(definitions *low.Definitions) *Definitions {
defs.Set(value.key, value.result) defs.Set(value.key, value.result)
return nil return nil
} }
_ = orderedmap.TranslateMapParallel(definitions.Schemas, translateFunc, resultFunc) _ = datamodel.TranslateMapParallel(definitions.Schemas, translateFunc, resultFunc)
rd.Definitions = defs rd.Definitions = defs
return rd return rd
} }

View File

@@ -6,13 +6,14 @@ package v2
import ( import (
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Example represents a high-level Swagger / OpenAPI 2 Example object, backed by a low level one. // Example represents a high-level Swagger / OpenAPI 2 Example object, backed by a low level one.
// Allows sharing examples for operation responses // Allows sharing examples for operation responses
// - https://swagger.io/specification/v2/#exampleObject // - https://swagger.io/specification/v2/#exampleObject
type Example struct { type Example struct {
Values orderedmap.Map[string, any] Values *orderedmap.Map[string, *yaml.Node]
low *low.Examples low *low.Examples
} }
@@ -21,7 +22,7 @@ func NewExample(examples *low.Examples) *Example {
e := new(Example) e := new(Example)
e.low = examples e.low = examples
if orderedmap.Len(examples.Values) > 0 { if orderedmap.Len(examples.Values) > 0 {
values := orderedmap.New[string, any]() values := orderedmap.New[string, *yaml.Node]()
for pair := orderedmap.First(examples.Values); pair != nil; pair = pair.Next() { for pair := orderedmap.First(examples.Values); pair != nil; pair = pair.Next() {
values.Set(pair.Key().Value, pair.Value().Value) values.Set(pair.Key().Value, pair.Value().Value)
} }

View File

@@ -6,6 +6,8 @@ package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Header Represents a high-level Swagger / OpenAPI 2 Header object, backed by a low-level one. // Header Represents a high-level Swagger / OpenAPI 2 Header object, backed by a low-level one.
@@ -30,7 +32,7 @@ type Header struct {
UniqueItems bool UniqueItems bool
Enum []any Enum []any
MultipleOf int MultipleOf int
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Header low *low.Header
} }

View File

@@ -5,6 +5,7 @@ package v2
import ( import (
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"gopkg.in/yaml.v3"
) )
// Items is a high-level representation of a Swagger / OpenAPI 2 Items object, backed by a low level one. // Items is a high-level representation of a Swagger / OpenAPI 2 Items object, backed by a low level one.
@@ -16,7 +17,7 @@ type Items struct {
Format string Format string
CollectionFormat string CollectionFormat string
Items *Items Items *Items
Default any Default *yaml.Node
Maximum int Maximum int
ExclusiveMaximum bool ExclusiveMaximum bool
Minimum int Minimum int
@@ -27,7 +28,7 @@ type Items struct {
MaxItems int MaxItems int
MinItems int MinItems int
UniqueItems bool UniqueItems bool
Enum []any Enum []*yaml.Node
MultipleOf int MultipleOf int
low *low.Items low *low.Items
} }
@@ -82,7 +83,7 @@ func NewItems(items *low.Items) *Items {
i.UniqueItems = items.UniqueItems.Value i.UniqueItems = items.UniqueItems.Value
} }
if !items.Enum.IsEmpty() { if !items.Enum.IsEmpty() {
var enums []any var enums []*yaml.Node
for e := range items.Enum.Value { for e := range items.Enum.Value {
enums = append(enums, items.Enum.Value[e].Value) enums = append(enums, items.Enum.Value[e].Value)
} }

View File

@@ -7,6 +7,8 @@ import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Operation represents a high-level Swagger / OpenAPI 2 Operation object, backed by a low-level one. // Operation represents a high-level Swagger / OpenAPI 2 Operation object, backed by a low-level one.
@@ -25,7 +27,7 @@ type Operation struct {
Schemes []string Schemes []string
Deprecated bool Deprecated bool
Security []*base.SecurityRequirement Security []*base.SecurityRequirement
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Operation low *low.Operation
} }

View File

@@ -7,6 +7,8 @@ import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Parameter represents a high-level Swagger / OpenAPI 2 Parameter object, backed by a low-level one. // Parameter represents a high-level Swagger / OpenAPI 2 Parameter object, backed by a low-level one.
@@ -61,7 +63,7 @@ type Parameter struct {
Schema *base.SchemaProxy Schema *base.SchemaProxy
Items *Items Items *Items
CollectionFormat string CollectionFormat string
Default any Default *yaml.Node
Maximum *int Maximum *int
ExclusiveMaximum *bool ExclusiveMaximum *bool
Minimum *int Minimum *int
@@ -72,9 +74,9 @@ type Parameter struct {
MaxItems *int MaxItems *int
MinItems *int MinItems *int
UniqueItems *bool UniqueItems *bool
Enum []any Enum []*yaml.Node
MultipleOf *int MultipleOf *int
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Parameter low *low.Parameter
} }
@@ -147,7 +149,7 @@ func NewParameter(parameter *low.Parameter) *Parameter {
p.UniqueItems = &parameter.UniqueItems.Value p.UniqueItems = &parameter.UniqueItems.Value
} }
if !parameter.Enum.IsEmpty() { if !parameter.Enum.IsEmpty() {
var enums []any var enums []*yaml.Node
for e := range parameter.Enum.Value { for e := range parameter.Enum.Value {
enums = append(enums, parameter.Enum.Value[e].Value) enums = append(enums, parameter.Enum.Value[e].Value)
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
@@ -16,7 +17,7 @@ import (
// referenced to the ones defined here. It does not define global operation parameters // referenced to the ones defined here. It does not define global operation parameters
// - https://swagger.io/specification/v2/#parametersDefinitionsObject // - https://swagger.io/specification/v2/#parametersDefinitionsObject
type ParameterDefinitions struct { type ParameterDefinitions struct {
Definitions orderedmap.Map[string, *Parameter] Definitions *orderedmap.Map[string, *Parameter]
low *low.ParameterDefinitions low *low.ParameterDefinitions
} }
@@ -36,7 +37,7 @@ func NewParametersDefinitions(parametersDefinitions *low.ParameterDefinitions) *
params.Set(value.key, value.result) params.Set(value.key, value.result)
return nil return nil
} }
_ = orderedmap.TranslateMapParallel(parametersDefinitions.Definitions, translateFunc, resultFunc) _ = datamodel.TranslateMapParallel(parametersDefinitions.Definitions, translateFunc, resultFunc)
pd.Definitions = params pd.Definitions = params
return pd return pd
} }

View File

@@ -4,10 +4,15 @@
package v2 package v2
import ( import (
"reflect"
"slices"
"sync" "sync"
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low"
lowV2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// PathItem represents a high-level Swagger / OpenAPI 2 PathItem object backed by a low-level one. // PathItem represents a high-level Swagger / OpenAPI 2 PathItem object backed by a low-level one.
@@ -26,12 +31,12 @@ type PathItem struct {
Head *Operation Head *Operation
Patch *Operation Patch *Operation
Parameters []*Parameter Parameters []*Parameter
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.PathItem low *lowV2.PathItem
} }
// NewPathItem will create a new high-level PathItem from a low-level one. All paths are built out asynchronously. // NewPathItem will create a new high-level PathItem from a low-level one. All paths are built out asynchronously.
func NewPathItem(pathItem *low.PathItem) *PathItem { func NewPathItem(pathItem *lowV2.PathItem) *PathItem {
p := new(PathItem) p := new(PathItem)
p.low = pathItem p.low = pathItem
p.Extensions = high.ExtractExtensions(pathItem.Extensions) p.Extensions = high.ExtractExtensions(pathItem.Extensions)
@@ -42,7 +47,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
} }
p.Parameters = params p.Parameters = params
} }
var buildOperation = func(method string, op *low.Operation) *Operation { buildOperation := func(method string, op *lowV2.Operation) *Operation {
return NewOperation(op) return NewOperation(op)
} }
@@ -50,49 +55,49 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
if !pathItem.Get.IsEmpty() { if !pathItem.Get.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Get = buildOperation(low.GetLabel, pathItem.Get.Value) p.Get = buildOperation(lowV2.GetLabel, pathItem.Get.Value)
wg.Done() wg.Done()
}() }()
} }
if !pathItem.Put.IsEmpty() { if !pathItem.Put.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Put = buildOperation(low.PutLabel, pathItem.Put.Value) p.Put = buildOperation(lowV2.PutLabel, pathItem.Put.Value)
wg.Done() wg.Done()
}() }()
} }
if !pathItem.Post.IsEmpty() { if !pathItem.Post.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Post = buildOperation(low.PostLabel, pathItem.Post.Value) p.Post = buildOperation(lowV2.PostLabel, pathItem.Post.Value)
wg.Done() wg.Done()
}() }()
} }
if !pathItem.Patch.IsEmpty() { if !pathItem.Patch.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Patch = buildOperation(low.PatchLabel, pathItem.Patch.Value) p.Patch = buildOperation(lowV2.PatchLabel, pathItem.Patch.Value)
wg.Done() wg.Done()
}() }()
} }
if !pathItem.Delete.IsEmpty() { if !pathItem.Delete.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Delete = buildOperation(low.DeleteLabel, pathItem.Delete.Value) p.Delete = buildOperation(lowV2.DeleteLabel, pathItem.Delete.Value)
wg.Done() wg.Done()
}() }()
} }
if !pathItem.Head.IsEmpty() { if !pathItem.Head.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Head = buildOperation(low.HeadLabel, pathItem.Head.Value) p.Head = buildOperation(lowV2.HeadLabel, pathItem.Head.Value)
wg.Done() wg.Done()
}() }()
} }
if !pathItem.Options.IsEmpty() { if !pathItem.Options.IsEmpty() {
wg.Add(1) wg.Add(1)
go func() { go func() {
p.Options = buildOperation(low.OptionsLabel, pathItem.Options.Value) p.Options = buildOperation(lowV2.OptionsLabel, pathItem.Options.Value)
wg.Done() wg.Done()
}() }()
} }
@@ -101,32 +106,65 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
} }
// GoLow returns the low-level PathItem used to create the high-level one. // GoLow returns the low-level PathItem used to create the high-level one.
func (p *PathItem) GoLow() *low.PathItem { func (p *PathItem) GoLow() *lowV2.PathItem {
return p.low return p.low
} }
func (p *PathItem) GetOperations() map[string]*Operation { func (p *PathItem) GetOperations() *orderedmap.Map[string, *Operation] {
o := make(map[string]*Operation) o := orderedmap.New[string, *Operation]()
// TODO: this is a bit of a hack, but it works for now. We might just want to actually pull the data out of the document as a map and split it into the individual operations
type op struct {
name string
op *Operation
line int
}
getLine := func(field string, idx int) int {
if p.GoLow() == nil {
return idx
}
l, ok := reflect.ValueOf(p.GoLow()).Elem().FieldByName(field).Interface().(low.NodeReference[*lowV2.Operation])
if !ok || l.GetKeyNode() == nil {
return idx
}
return l.GetKeyNode().Line
}
ops := []op{}
if p.Get != nil { if p.Get != nil {
o[low.GetLabel] = p.Get ops = append(ops, op{name: lowV2.GetLabel, op: p.Get, line: getLine("Get", -7)})
} }
if p.Put != nil { if p.Put != nil {
o[low.PutLabel] = p.Put ops = append(ops, op{name: lowV2.PutLabel, op: p.Put, line: getLine("Put", -6)})
} }
if p.Post != nil { if p.Post != nil {
o[low.PostLabel] = p.Post ops = append(ops, op{name: lowV2.PostLabel, op: p.Post, line: getLine("Post", -5)})
} }
if p.Delete != nil { if p.Delete != nil {
o[low.DeleteLabel] = p.Delete ops = append(ops, op{name: lowV2.DeleteLabel, op: p.Delete, line: getLine("Delete", -4)})
} }
if p.Options != nil { if p.Options != nil {
o[low.OptionsLabel] = p.Options ops = append(ops, op{name: lowV2.OptionsLabel, op: p.Options, line: getLine("Options", -3)})
} }
if p.Head != nil { if p.Head != nil {
o[low.HeadLabel] = p.Head ops = append(ops, op{name: lowV2.HeadLabel, op: p.Head, line: getLine("Head", -2)})
} }
if p.Patch != nil { if p.Patch != nil {
o[low.PatchLabel] = p.Patch ops = append(ops, op{name: lowV2.PatchLabel, op: p.Patch, line: getLine("Patch", -1)})
} }
slices.SortStableFunc(ops, func(a op, b op) int {
return a.line - b.line
})
for _, op := range ops {
o.Set(op.name, op.op)
}
return o return o
} }

View File

@@ -5,16 +5,17 @@ package v2
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestPathItem_GetOperations(t *testing.T) { func TestPathItem_GetOperations(t *testing.T) {
yml := `get: yml := `get:
description: get description: get
put: put:
@@ -41,5 +42,5 @@ options:
r := NewPathItem(&n) r := NewPathItem(&n)
assert.Len(t, r.GetOperations(), 7) assert.Equal(t, 7, orderedmap.Len(r.GetOperations()))
} }

View File

@@ -9,12 +9,13 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v2low "github.com/pb33f/libopenapi/datamodel/low/v2" v2low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Paths represents a high-level Swagger / OpenAPI Paths object, backed by a low-level one. // Paths represents a high-level Swagger / OpenAPI Paths object, backed by a low-level one.
type Paths struct { type Paths struct {
PathItems orderedmap.Map[string, *PathItem] PathItems *orderedmap.Map[string, *PathItem]
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *v2low.Paths low *v2low.Paths
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one. // Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one.
@@ -16,9 +17,9 @@ import (
type Response struct { type Response struct {
Description string Description string
Schema *base.SchemaProxy Schema *base.SchemaProxy
Headers orderedmap.Map[string, *Header] Headers *orderedmap.Map[string, *Header]
Examples *Example Examples *Example
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Response low *low.Response
} }

View File

@@ -4,17 +4,19 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Responses is a high-level representation of a Swagger / OpenAPI 2 Responses object, backed by a low level one. // Responses is a high-level representation of a Swagger / OpenAPI 2 Responses object, backed by a low level one.
type Responses struct { type Responses struct {
Codes orderedmap.Map[string, *Response] Codes *orderedmap.Map[string, *Response]
Default *Response Default *Response
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Responses low *low.Responses
} }
@@ -40,7 +42,7 @@ func NewResponses(responses *low.Responses) *Responses {
resp.Set(value.key, value.result) resp.Set(value.key, value.result)
return nil return nil
} }
_ = orderedmap.TranslateMapParallel(responses.Codes, translateFunc, resultFunc) _ = datamodel.TranslateMapParallel(responses.Codes, translateFunc, resultFunc)
r.Codes = resp r.Codes = resp
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
@@ -16,7 +17,7 @@ import (
// referenced to the ones defined here. It does not define global operation responses // referenced to the ones defined here. It does not define global operation responses
// - https://swagger.io/specification/v2/#responsesDefinitionsObject // - https://swagger.io/specification/v2/#responsesDefinitionsObject
type ResponsesDefinitions struct { type ResponsesDefinitions struct {
Definitions orderedmap.Map[string, *Response] Definitions *orderedmap.Map[string, *Response]
low *low.ResponsesDefinitions low *low.ResponsesDefinitions
} }
@@ -36,7 +37,7 @@ func NewResponsesDefinitions(responsesDefinitions *low.ResponsesDefinitions) *Re
return nil return nil
} }
_ = orderedmap.TranslateMapParallel(responsesDefinitions.Definitions, translateFunc, resultFunc) _ = datamodel.TranslateMapParallel(responsesDefinitions.Definitions, translateFunc, resultFunc)
rd.Definitions = responses rd.Definitions = responses
return rd return rd
} }

View File

@@ -13,7 +13,7 @@ import (
// Scopes lists the available scopes for an OAuth2 security scheme. // Scopes lists the available scopes for an OAuth2 security scheme.
// - https://swagger.io/specification/v2/#scopesObject // - https://swagger.io/specification/v2/#scopesObject
type Scopes struct { type Scopes struct {
Values orderedmap.Map[string, string] Values *orderedmap.Map[string, string]
low *low.Scopes low *low.Scopes
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
@@ -16,7 +17,7 @@ import (
// schemes on the operations and only serves to provide the relevant details for each scheme // schemes on the operations and only serves to provide the relevant details for each scheme
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityDefinitions struct { type SecurityDefinitions struct {
Definitions orderedmap.Map[string, *SecurityScheme] Definitions *orderedmap.Map[string, *SecurityScheme]
low *low.SecurityDefinitions low *low.SecurityDefinitions
} }
@@ -35,12 +36,8 @@ func NewSecurityDefinitions(definitions *low.SecurityDefinitions) *SecurityDefin
schemes.Set(value.key, value.result) schemes.Set(value.key, value.result)
return nil return nil
} }
_ = orderedmap.TranslateMapParallel(definitions.Definitions, translateFunc, resultFunc) _ = datamodel.TranslateMapParallel(definitions.Definitions, translateFunc, resultFunc)
// schemes := make(map[string]*SecurityScheme)
// for k := range definitions.Definitions {
// schemes[k.Value] = NewSecurityScheme(definitions.Definitions[k].Value)
// }
sd.Definitions = schemes sd.Definitions = schemes
return sd return sd
} }

View File

@@ -6,6 +6,8 @@ package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// SecurityScheme is a high-level representation of a Swagger / OpenAPI 2 SecurityScheme object // SecurityScheme is a high-level representation of a Swagger / OpenAPI 2 SecurityScheme object
@@ -24,7 +26,7 @@ type SecurityScheme struct {
AuthorizationUrl string AuthorizationUrl string
TokenUrl string TokenUrl string
Scopes *Scopes Scopes *Scopes
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.SecurityScheme low *low.SecurityScheme
} }

View File

@@ -15,11 +15,12 @@ import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
) )
// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification. // Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification.
type Swagger struct { type Swagger struct {
// Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition. // Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition.
Swagger string Swagger string
@@ -90,7 +91,7 @@ type Swagger struct {
ExternalDocs *base.ExternalDoc ExternalDocs *base.ExternalDoc
// Extensions contains all custom extensions defined for the top-level document. // Extensions contains all custom extensions defined for the top-level document.
Extensions map[string]any Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Swagger low *low.Swagger
} }

View File

@@ -5,13 +5,12 @@ package v2
import ( import (
"os" "os"
"testing"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing"
) )
var doc *v2.Swagger var doc *v2.Swagger
@@ -42,8 +41,12 @@ func BenchmarkNewDocument(b *testing.B) {
func TestNewSwaggerDocument_Base(t *testing.T) { func TestNewSwaggerDocument_Base(t *testing.T) {
initTest() initTest()
highDoc := NewSwaggerDocument(doc) highDoc := NewSwaggerDocument(doc)
var xPet bool
_ = highDoc.Extensions.GetOrZero("x-pet").Decode(&xPet)
assert.Equal(t, "2.0", highDoc.Swagger) assert.Equal(t, "2.0", highDoc.Swagger)
assert.True(t, highDoc.Extensions["x-pet"].(bool)) assert.True(t, xPet)
assert.Equal(t, "petstore.swagger.io", highDoc.Host) assert.Equal(t, "petstore.swagger.io", highDoc.Host)
assert.Equal(t, "/v2", highDoc.BasePath) assert.Equal(t, "/v2", highDoc.BasePath)
assert.Len(t, highDoc.Schemes, 2) assert.Len(t, highDoc.Schemes, 2)
@@ -56,7 +59,6 @@ func TestNewSwaggerDocument_Base(t *testing.T) {
wentLow := highDoc.GoLow() wentLow := highDoc.GoLow()
assert.Equal(t, 16, wentLow.Host.ValueNode.Line) assert.Equal(t, 16, wentLow.Host.ValueNode.Line)
assert.Equal(t, 7, wentLow.Host.ValueNode.Column) assert.Equal(t, 7, wentLow.Host.ValueNode.Column)
} }
func TestNewSwaggerDocument_Info(t *testing.T) { func TestNewSwaggerDocument_Info(t *testing.T) {
@@ -82,11 +84,15 @@ func TestNewSwaggerDocument_Parameters(t *testing.T) {
initTest() initTest()
highDoc := NewSwaggerDocument(doc) highDoc := NewSwaggerDocument(doc)
params := highDoc.Parameters params := highDoc.Parameters
var xChicken string
_ = params.Definitions.GetOrZero("simpleParam").Extensions.GetOrZero("x-chicken").Decode(&xChicken)
assert.Equal(t, 1, orderedmap.Len(params.Definitions)) assert.Equal(t, 1, orderedmap.Len(params.Definitions))
assert.Equal(t, "query", params.Definitions.GetOrZero("simpleParam").In) assert.Equal(t, "query", params.Definitions.GetOrZero("simpleParam").In)
assert.Equal(t, "simple", params.Definitions.GetOrZero("simpleParam").Name) assert.Equal(t, "simple", params.Definitions.GetOrZero("simpleParam").Name)
assert.Equal(t, "string", params.Definitions.GetOrZero("simpleParam").Type) assert.Equal(t, "string", params.Definitions.GetOrZero("simpleParam").Type)
assert.Equal(t, "nuggets", params.Definitions.GetOrZero("simpleParam").Extensions["x-chicken"]) assert.Equal(t, "nuggets", xChicken)
wentLow := params.GoLow() wentLow := params.GoLow()
assert.Equal(t, 20, wentLow.FindParameter("simpleParam").ValueNode.Line) assert.Equal(t, 20, wentLow.FindParameter("simpleParam").ValueNode.Line)
@@ -95,7 +101,6 @@ func TestNewSwaggerDocument_Parameters(t *testing.T) {
wentLower := params.Definitions.GetOrZero("simpleParam").GoLow() wentLower := params.Definitions.GetOrZero("simpleParam").GoLow()
assert.Equal(t, 21, wentLower.Name.ValueNode.Line) assert.Equal(t, 21, wentLower.Name.ValueNode.Line)
assert.Equal(t, 11, wentLower.Name.ValueNode.Column) assert.Equal(t, 11, wentLower.Name.ValueNode.Column)
} }
func TestNewSwaggerDocument_Security(t *testing.T) { func TestNewSwaggerDocument_Security(t *testing.T) {
@@ -107,7 +112,6 @@ func TestNewSwaggerDocument_Security(t *testing.T) {
wentLow := highDoc.Security[0].GoLow() wentLow := highDoc.Security[0].GoLow()
assert.Equal(t, 25, wentLow.Requirements.ValueNode.Line) assert.Equal(t, 25, wentLow.Requirements.ValueNode.Line)
assert.Equal(t, 5, wentLow.Requirements.ValueNode.Column) assert.Equal(t, 5, wentLow.Requirements.ValueNode.Column)
} }
func TestNewSwaggerDocument_Definitions_Security(t *testing.T) { func TestNewSwaggerDocument_Definitions_Security(t *testing.T) {
@@ -140,22 +144,32 @@ func TestNewSwaggerDocument_Definitions_Responses(t *testing.T) {
assert.Equal(t, 2, orderedmap.Len(highDoc.Responses.Definitions)) assert.Equal(t, 2, orderedmap.Len(highDoc.Responses.Definitions))
defs := highDoc.Responses.Definitions defs := highDoc.Responses.Definitions
assert.Equal(t, "morning", defs.GetOrZero("200").Extensions["x-coffee"])
var xCoffee string
_ = defs.GetOrZero("200").Extensions.GetOrZero("x-coffee").Decode(&xCoffee)
assert.Equal(t, "morning", xCoffee)
assert.Equal(t, "OK", defs.GetOrZero("200").Description) assert.Equal(t, "OK", defs.GetOrZero("200").Description)
assert.Equal(t, "a generic API response object", assert.Equal(t, "a generic API response object",
defs.GetOrZero("200").Schema.Schema().Description) defs.GetOrZero("200").Schema.Schema().Description)
assert.Equal(t, 3, orderedmap.Len(defs.GetOrZero("200").Examples.Values)) assert.Equal(t, 3, orderedmap.Len(defs.GetOrZero("200").Examples.Values))
exp := defs.GetOrZero("200").Examples.Values.GetOrZero("application/json") var appJson map[string]interface{}
assert.Len(t, exp.(map[string]interface{}), 2) _ = defs.GetOrZero("200").Examples.Values.GetOrZero("application/json").Decode(&appJson)
assert.Equal(t, "two", exp.(map[string]interface{})["one"])
exp = defs.GetOrZero("200").Examples.Values.GetOrZero("text/xml") assert.Len(t, appJson, 2)
assert.Len(t, exp.([]interface{}), 3) assert.Equal(t, "two", appJson["one"])
assert.Equal(t, "two", exp.([]interface{})[1])
exp = defs.GetOrZero("200").Examples.Values.GetOrZero("text/plain") var textXml []interface{}
assert.Equal(t, "something else.", exp) _ = defs.GetOrZero("200").Examples.Values.GetOrZero("text/xml").Decode(&textXml)
assert.Len(t, textXml, 3)
assert.Equal(t, "two", textXml[1])
var textPlain string
_ = defs.GetOrZero("200").Examples.Values.GetOrZero("text/plain").Decode(&textPlain)
assert.Equal(t, "something else.", textPlain)
expWentLow := defs.GetOrZero("200").Examples.GoLow() expWentLow := defs.GetOrZero("200").Examples.GoLow()
assert.Equal(t, 702, expWentLow.FindExample("application/json").ValueNode.Line) assert.Equal(t, 702, expWentLow.FindExample("application/json").ValueNode.Line)
@@ -168,10 +182,13 @@ func TestNewSwaggerDocument_Definitions_Responses(t *testing.T) {
assert.Len(t, y.Enum, 2) assert.Len(t, y.Enum, 2)
x := y.Items x := y.Items
var def string
_ = x.Default.Decode(&def)
assert.Equal(t, "something", x.Format) assert.Equal(t, "something", x.Format)
assert.Equal(t, "array", x.Type) assert.Equal(t, "array", x.Type)
assert.Equal(t, "csv", x.CollectionFormat) assert.Equal(t, "csv", x.CollectionFormat)
assert.Equal(t, "cake", x.Default) assert.Equal(t, "cake", def)
assert.Equal(t, 10, x.Maximum) assert.Equal(t, 10, x.Maximum)
assert.Equal(t, 1, x.Minimum) assert.Equal(t, 1, x.Minimum)
assert.True(t, x.ExclusiveMaximum) assert.True(t, x.ExclusiveMaximum)
@@ -198,7 +215,6 @@ func TestNewSwaggerDocument_Definitions(t *testing.T) {
wentLow := highDoc.Definitions.GoLow() wentLow := highDoc.Definitions.GoLow()
assert.Equal(t, 848, wentLow.FindSchema("User").ValueNode.Line) assert.Equal(t, 848, wentLow.FindSchema("User").ValueNode.Line)
} }
func TestNewSwaggerDocument_Paths(t *testing.T) { func TestNewSwaggerDocument_Paths(t *testing.T) {
@@ -207,7 +223,14 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
assert.Equal(t, 15, orderedmap.Len(highDoc.Paths.PathItems)) assert.Equal(t, 15, orderedmap.Len(highDoc.Paths.PathItems))
upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage") upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage")
assert.Equal(t, "man", upload.Extensions["x-potato"])
var xPotato string
_ = upload.Extensions.GetOrZero("x-potato").Decode(&xPotato)
var paramEnum0 string
_ = upload.Post.Parameters[0].Enum[0].Decode(&paramEnum0)
assert.Equal(t, "man", xPotato)
assert.Nil(t, upload.Get) assert.Nil(t, upload.Get)
assert.Nil(t, upload.Put) assert.Nil(t, upload.Put)
assert.Nil(t, upload.Patch) assert.Nil(t, upload.Patch)
@@ -238,8 +261,11 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
assert.Equal(t, 20, *upload.Post.Parameters[0].MaxItems) assert.Equal(t, 20, *upload.Post.Parameters[0].MaxItems)
assert.True(t, *upload.Post.Parameters[0].UniqueItems) assert.True(t, *upload.Post.Parameters[0].UniqueItems)
assert.Len(t, upload.Post.Parameters[0].Enum, 2) assert.Len(t, upload.Post.Parameters[0].Enum, 2)
assert.Equal(t, "hello", upload.Post.Parameters[0].Enum[0]) assert.Equal(t, "hello", paramEnum0)
def := upload.Post.Parameters[0].Default.(map[string]interface{})
var def map[string]any
_ = upload.Post.Parameters[0].Default.Decode(&def)
assert.Equal(t, "here", def["something"]) assert.Equal(t, "here", def["something"])
assert.Equal(t, "https://pb33f.io", upload.Post.ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", upload.Post.ExternalDocs.URL)
@@ -257,11 +283,9 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
wentLowest := upload.Post.GoLow() wentLowest := upload.Post.GoLow()
assert.Equal(t, 55, wentLowest.Tags.KeyNode.Line) assert.Equal(t, 55, wentLowest.Tags.KeyNode.Line)
} }
func TestNewSwaggerDocument_Responses(t *testing.T) { func TestNewSwaggerDocument_Responses(t *testing.T) {
initTest() initTest()
highDoc := NewSwaggerDocument(doc) highDoc := NewSwaggerDocument(doc)
upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage").Post upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage").Post
@@ -278,5 +302,4 @@ func TestNewSwaggerDocument_Responses(t *testing.T) {
wentLower := OK.GoLow() wentLower := OK.GoLow()
assert.Equal(t, 107, wentLower.Schema.KeyNode.Line) assert.Equal(t, 107, wentLower.Schema.KeyNode.Line)
assert.Equal(t, 11, wentLower.Schema.KeyNode.Column) assert.Equal(t, 11, wentLower.Schema.KeyNode.Column)
} }

View File

@@ -21,8 +21,8 @@ import (
// that identifies a URL to use for the callback operation. // that identifies a URL to use for the callback operation.
// - https://spec.openapis.org/oas/v3.1.0#callback-object // - https://spec.openapis.org/oas/v3.1.0#callback-object
type Callback struct { type Callback struct {
Expression orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"` Expression *orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Callback low *low.Callback
} }
@@ -31,13 +31,11 @@ func NewCallback(lowCallback *low.Callback) *Callback {
n := new(Callback) n := new(Callback)
n.low = lowCallback n.low = lowCallback
n.Expression = orderedmap.New[string, *PathItem]() n.Expression = orderedmap.New[string, *PathItem]()
for pair := orderedmap.First(lowCallback.Expression.Value); pair != nil; pair = pair.Next() { for pair := orderedmap.First(lowCallback.Expression); pair != nil; pair = pair.Next() {
n.Expression.Set(pair.Key().Value, NewPathItem(pair.Value().Value)) n.Expression.Set(pair.Key().Value, NewPathItem(pair.Value().Value))
} }
n.Extensions = make(map[string]any)
for k, v := range lowCallback.Extensions { n.Extensions = high.ExtractExtensions(lowCallback.Extensions)
n.Extensions[k.Value] = v.Value
}
return n return n
} }
@@ -51,36 +49,50 @@ func (c *Callback) GoLowUntyped() any {
return c.low return c.low
} }
// Render will return a YAML representation of the Callback object as a byte slice. // Render will return a YAML representation of the Paths object as a byte slice.
func (c *Callback) Render() ([]byte, error) { func (c *Callback) Render() ([]byte, error) {
return yaml.Marshal(c) return yaml.Marshal(c)
} }
// MarshalYAML will create a ready to render YAML representation of the Callback object. func (c *Callback) RenderInline() ([]byte, error) {
d, _ := c.MarshalYAMLInline()
return yaml.Marshal(d)
}
// MarshalYAML will create a ready to render YAML representation of the Paths object.
func (c *Callback) MarshalYAML() (interface{}, error) { func (c *Callback) MarshalYAML() (interface{}, error) {
// map keys correctly. // map keys correctly.
m := utils.CreateEmptyMapNode() m := utils.CreateEmptyMapNode()
type cbItem struct { type pathItem struct {
cb *PathItem pi *PathItem
exp string path string
line int line int
ext *yaml.Node style yaml.Style
rendered *yaml.Node
} }
var mapped []*cbItem var mapped []*pathItem
for pair := orderedmap.First(c.Expression); pair != nil; pair = pair.Next() { for pair := orderedmap.First(c.Expression); pair != nil; pair = pair.Next() {
ln := 999 // default to a high value to weight new content to the bottom. k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if c.low != nil { if c.low != nil {
for lPair := orderedmap.First(c.low.Expression.Value); lPair != nil; lPair = lPair.Next() { lpi := c.low.FindExpression(k)
if lPair.Key().Value == pair.Key() { if lpi != nil {
ln = lPair.Key().KeyNode.Line ln = lpi.ValueNode.Line
} }
}
} for pair := orderedmap.First(c.low.Expression); pair != nil; pair = pair.Next() {
mapped = append(mapped, &cbItem{pair.Value(), pair.Key(), ln, nil}) if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
}
}
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
} }
// extract extensions
nb := high.NewNodeBuilder(c, c.low) nb := high.NewNodeBuilder(c, c.low)
extNode := nb.Render() extNode := nb.Render()
if extNode != nil && extNode.Content != nil { if extNode != nil && extNode.Content != nil {
@@ -90,23 +102,101 @@ func (c *Callback) MarshalYAML() (interface{}, error) {
label = extNode.Content[u].Value label = extNode.Content[u].Value
continue continue
} }
mapped = append(mapped, &cbItem{nil, label, mapped = append(mapped, &pathItem{
extNode.Content[u].Line, extNode.Content[u]}) nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
} }
} }
sort.Slice(mapped, func(i, j int) bool { sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line return mapped[i].line < mapped[j].line
}) })
for j := range mapped { for _, mp := range mapped {
if mapped[j].cb != nil { if mp.pi != nil {
rendered, _ := mapped[j].cb.MarshalYAML() rendered, _ := mp.pi.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].exp))
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node)) m.Content = append(m.Content, rendered.(*yaml.Node))
} }
if mapped[j].ext != nil { if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].exp)) m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mapped[j].ext) m.Content = append(m.Content, mp.rendered)
}
}
return m, nil
}
func (c *Callback) MarshalYAMLInline() (interface{}, error) {
// map keys correctly.
m := utils.CreateEmptyMapNode()
type pathItem struct {
pi *PathItem
path string
line int
style yaml.Style
rendered *yaml.Node
}
var mapped []*pathItem
for pair := orderedmap.First(c.Expression); pair != nil; pair = pair.Next() {
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if c.low != nil {
lpi := c.low.FindExpression(k)
if lpi != nil {
ln = lpi.ValueNode.Line
}
for pair := orderedmap.First(c.low.Expression); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
}
}
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
}
nb := high.NewNodeBuilder(c, c.low)
nb.Resolve = true
extNode := nb.Render()
if extNode != nil && extNode.Content != nil {
var label string
for u := range extNode.Content {
if u%2 == 0 {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &pathItem{
nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
}
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for _, mp := range mapped {
if mp.pi != nil {
rendered, _ := mp.pi.MarshalYAMLInline()
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mp.rendered)
} }
} }

View File

@@ -12,11 +12,14 @@ import (
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func TestCallback_MarshalYAML(t *testing.T) { func TestCallback_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
cb := &Callback{ cb := &Callback{
Expression: orderedmap.ToOrderedMap(map[string]*PathItem{ Expression: orderedmap.ToOrderedMap(map[string]*PathItem{
@@ -31,9 +34,7 @@ func TestCallback_MarshalYAML(t *testing.T) {
}, },
}, },
}), }),
Extensions: map[string]any{ Extensions: ext,
"x-burgers": "why not?",
},
} }
rend, _ := cb.Render() rend, _ := cb.Render()
@@ -43,7 +44,10 @@ func TestCallback_MarshalYAML(t *testing.T) {
// mutate // mutate
cb.Expression.GetOrZero("https://pb33f.io").Get.OperationId = "blim-blam" cb.Expression.GetOrZero("https://pb33f.io").Get.OperationId = "blim-blam"
cb.Extensions = map[string]interface{}{"x-burgers": "yes please!"}
ext = orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("yes please!"))
cb.Extensions = ext
rend, _ = cb.Render() rend, _ = cb.Render()
// there is no way to determine order in brand new maps, so we have to check length. // there is no way to determine order in brand new maps, so we have to check length.
@@ -72,7 +76,10 @@ func TestCallback_MarshalYAML(t *testing.T) {
r := NewCallback(&n) r := NewCallback(&n)
assert.Equal(t, "please", r.Extensions["x-break-everything"]) var xBreakEverything string
_ = r.Extensions.GetOrZero("x-break-everything").Decode(&xBreakEverything)
assert.Equal(t, "please", xBreakEverything)
rend, _ = r.Render() rend, _ = r.Render()
assert.Equal(t, k, strings.TrimSpace(string(rend))) assert.Equal(t, k, strings.TrimSpace(string(rend)))

View File

@@ -22,16 +22,16 @@ import (
// will have no effect on the API unless they are explicitly referenced from properties outside the components object. // will have no effect on the API unless they are explicitly referenced from properties outside the components object.
// - https://spec.openapis.org/oas/v3.1.0#components-object // - https://spec.openapis.org/oas/v3.1.0#components-object
type Components struct { type Components struct {
Schemas orderedmap.Map[string, *highbase.SchemaProxy] `json:"schemas,omitempty" yaml:"schemas,omitempty"` Schemas *orderedmap.Map[string, *highbase.SchemaProxy] `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Responses orderedmap.Map[string, *Response] `json:"responses,omitempty" yaml:"responses,omitempty"` Responses *orderedmap.Map[string, *Response] `json:"responses,omitempty" yaml:"responses,omitempty"`
Parameters orderedmap.Map[string, *Parameter] `json:"parameters,omitempty" yaml:"parameters,omitempty"` Parameters *orderedmap.Map[string, *Parameter] `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Examples orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` Examples *orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
RequestBodies orderedmap.Map[string, *RequestBody] `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` RequestBodies *orderedmap.Map[string, *RequestBody] `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"` Headers *orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
SecuritySchemes orderedmap.Map[string, *SecurityScheme] `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` SecuritySchemes *orderedmap.Map[string, *SecurityScheme] `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Links orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"` Links *orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` Callbacks *orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Components low *low.Components
} }
@@ -41,7 +41,7 @@ type Components struct {
func NewComponents(comp *low.Components) *Components { func NewComponents(comp *low.Components) *Components {
c := new(Components) c := new(Components)
c.low = comp c.low = comp
if len(comp.Extensions) > 0 { if orderedmap.Len(comp.Extensions) > 0 {
c.Extensions = high.ExtractExtensions(comp.Extensions) c.Extensions = high.ExtractExtensions(comp.Extensions)
} }
cbMap := orderedmap.New[string, *Callback]() cbMap := orderedmap.New[string, *Callback]()
@@ -114,7 +114,7 @@ type componentResult[T any] struct {
} }
// buildComponent builds component structs from low level structs. // buildComponent builds component structs from low level structs.
func buildComponent[IN any, OUT any](inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]], outMap orderedmap.Map[string, OUT], translateItem func(IN) OUT) { func buildComponent[IN any, OUT any](inMap *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]], outMap *orderedmap.Map[string, OUT], translateItem func(IN) OUT) {
translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]]) (componentResult[OUT], error) { translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]]) (componentResult[OUT], error) {
return componentResult[OUT]{key: pair.Key().Value, res: translateItem(pair.Value().Value)}, nil return componentResult[OUT]{key: pair.Key().Value, res: translateItem(pair.Value().Value)}, nil
} }
@@ -126,7 +126,7 @@ func buildComponent[IN any, OUT any](inMap orderedmap.Map[lowmodel.KeyReference[
} }
// buildSchema builds a schema from low level structs. // buildSchema builds a schema from low level structs.
func buildSchema(inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]], outMap orderedmap.Map[string, *highbase.SchemaProxy]) { func buildSchema(inMap *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]], outMap *orderedmap.Map[string, *highbase.SchemaProxy]) {
translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]]) (componentResult[*highbase.SchemaProxy], error) { translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]]) (componentResult[*highbase.SchemaProxy], error) {
value := pair.Value() value := pair.Value()
var sch *highbase.SchemaProxy var sch *highbase.SchemaProxy

View File

@@ -23,7 +23,6 @@ import (
// Document represents a high-level OpenAPI 3 document (both 3.0 & 3.1). A Document is the root of the specification. // Document represents a high-level OpenAPI 3 document (both 3.0 & 3.1). A Document is the root of the specification.
type Document struct { type Document struct {
// Version is the version of OpenAPI being used, extracted from the 'openapi: x.x.x' definition. // Version is the version of OpenAPI being used, extracted from the 'openapi: x.x.x' definition.
// This is not a standard property of the OpenAPI model, it's a convenience mechanism only. // This is not a standard property of the OpenAPI model, it's a convenience mechanism only.
Version string `json:"openapi,omitempty" yaml:"openapi,omitempty"` Version string `json:"openapi,omitempty" yaml:"openapi,omitempty"`
@@ -54,7 +53,7 @@ type Document struct {
// an empty security requirement ({}) can be included in the array. // an empty security requirement ({}) can be included in the array.
// - https://spec.openapis.org/oas/v3.1.0#security-requirement-object // - https://spec.openapis.org/oas/v3.1.0#security-requirement-object
Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"` Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"`
//Security []*base.SecurityRequirement `json:"-" yaml:"-"` // Security []*base.SecurityRequirement `json:"-" yaml:"-"`
// Tags is a slice of base.Tag instances defined by the specification // Tags is a slice of base.Tag instances defined by the specification
// A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on // A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on
@@ -69,7 +68,7 @@ type Document struct {
ExternalDocs *base.ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` ExternalDocs *base.ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Extensions contains all custom extensions defined for the top-level document. // Extensions contains all custom extensions defined for the top-level document.
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
// JsonSchemaDialect is a 3.1+ property that sets the dialect to use for validating *base.Schema definitions // JsonSchemaDialect is a 3.1+ property that sets the dialect to use for validating *base.Schema definitions
// The default value for the $schema keyword within Schema Objects contained within this OAS document. // The default value for the $schema keyword within Schema Objects contained within this OAS document.
@@ -83,7 +82,7 @@ type Document struct {
// for example by an out-of-band registration. The key name is a unique string to refer to each webhook, // for example by an out-of-band registration. The key name is a unique string to refer to each webhook,
// while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider // while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider
// and the expected responses. An example is available. // and the expected responses. An example is available.
Webhooks orderedmap.Map[string, *PathItem] `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` Webhooks *orderedmap.Map[string, *PathItem] `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
// Index is a reference to the *index.SpecIndex that was created for the document and used // Index is a reference to the *index.SpecIndex that was created for the document and used
// as a guide when building out the Document. Ideal if further processing is required on the model and // as a guide when building out the Document. Ideal if further processing is required on the model and
@@ -122,7 +121,7 @@ func NewDocument(document *low.Document) *Document {
if !document.ExternalDocs.IsEmpty() { if !document.ExternalDocs.IsEmpty() {
d.ExternalDocs = base.NewExternalDoc(document.ExternalDocs.Value) d.ExternalDocs = base.NewExternalDoc(document.ExternalDocs.Value)
} }
if len(document.Extensions) > 0 { if orderedmap.Len(document.Extensions) > 0 {
d.Extensions = high.ExtractExtensions(document.Extensions) d.Extensions = high.ExtractExtensions(document.Extensions)
} }
if !document.Components.IsEmpty() { if !document.Components.IsEmpty() {

View File

@@ -51,7 +51,11 @@ func BenchmarkNewDocument(b *testing.B) {
func TestNewDocument_Extensions(t *testing.T) { func TestNewDocument_Extensions(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Equal(t, "darkside", h.Extensions["x-something-something"])
var xSomethingSomething string
_ = h.Extensions.GetOrZero("x-something-something").Decode(&xSomethingSomething)
assert.Equal(t, "darkside", xSomethingSomething)
} }
func TestNewDocument_ExternalDocs(t *testing.T) { func TestNewDocument_ExternalDocs(t *testing.T) {
@@ -133,15 +137,28 @@ func TestNewDocument_Servers(t *testing.T) {
func TestNewDocument_Tags(t *testing.T) { func TestNewDocument_Tags(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
var xInternalTing string
_ = h.Tags[0].Extensions.GetOrZero("x-internal-ting").Decode(&xInternalTing)
var xInternalTong int64
_ = h.Tags[0].Extensions.GetOrZero("x-internal-tong").Decode(&xInternalTong)
var xInternalTang float64
_ = h.Tags[0].Extensions.GetOrZero("x-internal-tang").Decode(&xInternalTang)
assert.Len(t, h.Tags, 2) assert.Len(t, h.Tags, 2)
assert.Equal(t, "Burgers", h.Tags[0].Name) assert.Equal(t, "Burgers", h.Tags[0].Name)
assert.Equal(t, "All kinds of yummy burgers.", h.Tags[0].Description) assert.Equal(t, "All kinds of yummy burgers.", h.Tags[0].Description)
assert.Equal(t, "Find out more", h.Tags[0].ExternalDocs.Description) assert.Equal(t, "Find out more", h.Tags[0].ExternalDocs.Description)
assert.Equal(t, "https://pb33f.io", h.Tags[0].ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", h.Tags[0].ExternalDocs.URL)
assert.Equal(t, "somethingSpecial", h.Tags[0].Extensions["x-internal-ting"]) assert.Equal(t, "somethingSpecial", xInternalTing)
assert.Equal(t, int64(1), h.Tags[0].Extensions["x-internal-tong"]) assert.Equal(t, int64(1), xInternalTong)
assert.Equal(t, 1.2, h.Tags[0].Extensions["x-internal-tang"]) assert.Equal(t, 1.2, xInternalTang)
assert.True(t, h.Tags[0].Extensions["x-internal-tung"].(bool))
var tung bool
_ = h.Tags[0].Extensions.GetOrZero("x-internal-tung").Decode(&tung)
assert.True(t, tung)
wentLow := h.Tags[1].GoLow() wentLow := h.Tags[1].GoLow()
assert.Equal(t, 39, wentLow.Description.KeyNode.Line) assert.Equal(t, 39, wentLow.Description.KeyNode.Line)
@@ -191,7 +208,10 @@ func TestNewDocument_Components_Callbacks(t *testing.T) {
h.Components.Callbacks.GetOrZero("BurgerCallback").GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column, h.Components.Callbacks.GetOrZero("BurgerCallback").GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column,
) )
assert.Equal(t, "please", h.Components.Callbacks.GetOrZero("BurgerCallback").Extensions["x-break-everything"]) var xBreakEverything string
_ = h.Components.Callbacks.GetOrZero("BurgerCallback").Extensions.GetOrZero("x-break-everything").Decode(&xBreakEverything)
assert.Equal(t, "please", xBreakEverything)
for pair := orderedmap.First(h.Components.GoLow().Callbacks.Value); pair != nil; pair = pair.Next() { for pair := orderedmap.First(h.Components.GoLow().Callbacks.Value); pair != nil; pair = pair.Next() {
if pair.Key().Value == "BurgerCallback" { if pair.Key().Value == "BurgerCallback" {
@@ -209,7 +229,9 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
goLow := h.Components.GoLow() goLow := h.Components.GoLow()
a := h.Components.Schemas.GetOrZero("Error") a := h.Components.Schemas.GetOrZero("Error")
abcd := a.Schema().Properties.GetOrZero("message").Schema().Example
var abcd string
_ = a.Schema().Properties.GetOrZero("message").Schema().Example.Decode(&abcd)
assert.Equal(t, "No such burger as 'Big-Whopper'", abcd) assert.Equal(t, "No such burger as 'Big-Whopper'", abcd)
assert.Equal(t, 433, goLow.Schemas.KeyNode.Line) assert.Equal(t, 433, goLow.Schemas.KeyNode.Line)
assert.Equal(t, 3, goLow.Schemas.KeyNode.Column) assert.Equal(t, 3, goLow.Schemas.KeyNode.Column)
@@ -218,13 +240,21 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
b := h.Components.Schemas.GetOrZero("Burger") b := h.Components.Schemas.GetOrZero("Burger")
assert.Len(t, b.Schema().Required, 2) assert.Len(t, b.Schema().Required, 2)
assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties.GetOrZero("fries").Schema().Description) assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties.GetOrZero("fries").Schema().Description)
assert.Equal(t, int64(2), b.Schema().Properties.GetOrZero("numPatties").Schema().Example)
var numPattiesExample int64
_ = b.Schema().Properties.GetOrZero("numPatties").Schema().Example.Decode(&numPattiesExample)
assert.Equal(t, int64(2), numPattiesExample)
assert.Equal(t, 448, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line) assert.Equal(t, 448, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line)
assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column) assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column)
assert.Equal(t, 450, b.Schema().GoLow().FindProperty("name").ValueNode.Line) assert.Equal(t, 450, b.Schema().GoLow().FindProperty("name").ValueNode.Line)
f := h.Components.Schemas.GetOrZero("Fries") f := h.Components.Schemas.GetOrZero("Fries")
assert.Equal(t, "salt", f.Schema().Properties.GetOrZero("seasoning").Schema().Items.A.Schema().Example)
var seasoningExample string
_ = f.Schema().Properties.GetOrZero("seasoning").Schema().Items.A.Schema().Example.Decode(&seasoningExample)
assert.Equal(t, "salt", seasoningExample)
assert.Len(t, f.Schema().Properties.GetOrZero("favoriteDrink").Schema().Properties.GetOrZero("drinkType").Schema().Enum, 1) assert.Len(t, f.Schema().Properties.GetOrZero("favoriteDrink").Schema().Properties.GetOrZero("drinkType").Schema().Enum, 1)
d := h.Components.Schemas.GetOrZero("Drink") d := h.Components.Schemas.GetOrZero("Drink")
@@ -240,7 +270,11 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
assert.Equal(t, 523, pl.Schema().XML.GoLow().Name.ValueNode.Line) assert.Equal(t, 523, pl.Schema().XML.GoLow().Name.ValueNode.Line)
ext := h.Components.Extensions ext := h.Components.Extensions
assert.Equal(t, "loud", ext["x-screaming-baby"])
var xScreamingBaby string
_ = ext.GetOrZero("x-screaming-baby").Decode(&xScreamingBaby)
assert.Equal(t, "loud", xScreamingBaby)
} }
func TestNewDocument_Components_Headers(t *testing.T) { func TestNewDocument_Components_Headers(t *testing.T) {
@@ -319,8 +353,12 @@ func TestNewDocument_Components_Parameters(t *testing.T) {
assert.Equal(t, "burgerHeader", bh.Name) assert.Equal(t, "burgerHeader", bh.Name)
assert.Equal(t, 392, bh.GoLow().Name.KeyNode.Line) assert.Equal(t, 392, bh.GoLow().Name.KeyNode.Line)
assert.Equal(t, 2, orderedmap.Len(bh.Schema.Schema().Properties)) assert.Equal(t, 2, orderedmap.Len(bh.Schema.Schema().Properties))
assert.Equal(t, "big-mac", bh.Example)
assert.True(t, bh.Required) var example string
_ = bh.Example.Decode(&example)
assert.Equal(t, "big-mac", example)
assert.True(t, *bh.Required)
assert.Equal( assert.Equal(
t, t,
"this is a header", "this is a header",
@@ -341,8 +379,12 @@ func TestNewDocument_Paths(t *testing.T) {
func testBurgerShop(t *testing.T, h *Document, checkLines bool) { func testBurgerShop(t *testing.T, h *Document, checkLines bool) {
burgersOp := h.Paths.PathItems.GetOrZero("/burgers") burgersOp := h.Paths.PathItems.GetOrZero("/burgers")
assert.Len(t, burgersOp.GetOperations(), 1) assert.Equal(t, 1, burgersOp.GetOperations().Len())
assert.Equal(t, "meaty", burgersOp.Extensions["x-burger-meta"])
var xBurgerMeta string
_ = burgersOp.Extensions.GetOrZero("x-burger-meta").Decode(&xBurgerMeta)
assert.Equal(t, "meaty", xBurgerMeta)
assert.Nil(t, burgersOp.Get) assert.Nil(t, burgersOp.Get)
assert.Nil(t, burgersOp.Put) assert.Nil(t, burgersOp.Put)
assert.Nil(t, burgersOp.Patch) assert.Nil(t, burgersOp.Patch)

View File

@@ -15,7 +15,7 @@ import (
// - https://spec.openapis.org/oas/v3.1.0#encoding-object // - https://spec.openapis.org/oas/v3.1.0#encoding-object
type Encoding struct { type Encoding struct {
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"` Headers *orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"` Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
@@ -28,7 +28,9 @@ func NewEncoding(encoding *low.Encoding) *Encoding {
e.low = encoding e.low = encoding
e.ContentType = encoding.ContentType.Value e.ContentType = encoding.ContentType.Value
e.Style = encoding.Style.Value e.Style = encoding.Style.Value
if !encoding.Explode.IsEmpty() {
e.Explode = &encoding.Explode.Value e.Explode = &encoding.Explode.Value
}
e.AllowReserved = encoding.AllowReserved.Value e.AllowReserved = encoding.AllowReserved.Value
e.Headers = ExtractHeaders(encoding.Headers.Value) e.Headers = ExtractHeaders(encoding.Headers.Value)
return e return e
@@ -56,7 +58,7 @@ func (e *Encoding) MarshalYAML() (interface{}, error) {
} }
// ExtractEncoding converts hard to navigate low-level plumbing Encoding definitions, into a high-level simple map // ExtractEncoding converts hard to navigate low-level plumbing Encoding definitions, into a high-level simple map
func ExtractEncoding(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Encoding]]) orderedmap.Map[string, *Encoding] { func ExtractEncoding(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Encoding]]) *orderedmap.Map[string, *Encoding] {
extracted := orderedmap.New[string, *Encoding]() extracted := orderedmap.New[string, *Encoding]()
for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() { for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, NewEncoding(pair.Value().Value)) extracted.Set(pair.Key().Value, NewEncoding(pair.Value().Value))

View File

@@ -24,10 +24,10 @@ type Header struct {
Explode bool `json:"explode,omitempty" yaml:"explode,omitempty"` Explode bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Schema *highbase.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` Schema *highbase.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"` Example *yaml.Node `json:"example,omitempty" yaml:"example,omitempty"`
Examples orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` Examples *orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Header low *low.Header
} }
@@ -52,6 +52,7 @@ func NewHeader(header *low.Header) *Header {
h.Content = ExtractContent(header.Content.Value) h.Content = ExtractContent(header.Content.Value)
h.Example = header.Example.Value h.Example = header.Example.Value
h.Examples = highbase.ExtractExamples(header.Examples.Value) h.Examples = highbase.ExtractExamples(header.Examples.Value)
h.Extensions = high.ExtractExtensions(header.Extensions)
return h return h
} }
@@ -66,7 +67,7 @@ func (h *Header) GoLowUntyped() any {
} }
// ExtractHeaders will extract a hard to navigate low-level Header map, into simple high-level one. // ExtractHeaders will extract a hard to navigate low-level Header map, into simple high-level one.
func ExtractHeaders(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Header]]) orderedmap.Map[string, *Header] { func ExtractHeaders(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Header]]) *orderedmap.Map[string, *Header] {
extracted := orderedmap.New[string, *Header]() extracted := orderedmap.New[string, *Header]()
for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() { for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, NewHeader(pair.Value().Value)) extracted.Set(pair.Key().Value, NewHeader(pair.Value().Value))

View File

@@ -9,10 +9,14 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
) )
func TestHeader_MarshalYAML(t *testing.T) { func TestHeader_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
header := &Header{ header := &Header{
Description: "A header", Description: "A header",
@@ -22,11 +26,11 @@ func TestHeader_MarshalYAML(t *testing.T) {
Style: "simple", Style: "simple",
Explode: true, Explode: true,
AllowReserved: true, AllowReserved: true,
Example: "example", Example: utils.CreateStringNode("example"),
Examples: orderedmap.ToOrderedMap(map[string]*base.Example{ Examples: orderedmap.ToOrderedMap(map[string]*base.Example{
"example": {Value: "example"}, "example": {Value: utils.CreateStringNode("example")},
}), }),
Extensions: map[string]interface{}{"x-burgers": "why not?"}, Extensions: ext,
} }
rend, _ := header.Render() rend, _ := header.Render()
@@ -45,5 +49,4 @@ examples:
x-burgers: why not?` x-burgers: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }

View File

@@ -25,11 +25,11 @@ import (
type Link struct { type Link struct {
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters orderedmap.Map[string, string] `json:"parameters,omitempty" yaml:"parameters,omitempty"` Parameters *orderedmap.Map[string, string] `json:"parameters,omitempty" yaml:"parameters,omitempty"`
RequestBody string `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` RequestBody string `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Server *Server `json:"server,omitempty" yaml:"server,omitempty"` Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Link low *low.Link
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
@@ -19,9 +20,9 @@ import (
type MediaType struct { type MediaType struct {
Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"` Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` Examples *orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding orderedmap.Map[string, *Encoding] `json:"encoding,omitempty" yaml:"encoding,omitempty"` Encoding *orderedmap.Map[string, *Encoding] `json:"encoding,omitempty" yaml:"encoding,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.MediaType low *low.MediaType
} }
@@ -73,7 +74,7 @@ func (m *MediaType) MarshalYAMLInline() (interface{}, error) {
// ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler // ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler
// and easier to navigate high-level one. // and easier to navigate high-level one.
func ExtractContent(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) orderedmap.Map[string, *MediaType] { func ExtractContent(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) *orderedmap.Map[string, *MediaType] {
extracted := orderedmap.New[string, *MediaType]() extracted := orderedmap.New[string, *MediaType]()
translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) (asyncResult[*MediaType], error) { translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) (asyncResult[*MediaType], error) {
return asyncResult[*MediaType]{ return asyncResult[*MediaType]{
@@ -85,6 +86,6 @@ func ExtractContent(elements orderedmap.Map[lowmodel.KeyReference[string], lowmo
extracted.Set(value.key, value.result) extracted.Set(value.key, value.result)
return nil return nil
} }
_ = orderedmap.TranslateMapParallel(elements, translateFunc, resultFunc) _ = datamodel.TranslateMapParallel(elements, translateFunc, resultFunc)
return extracted return extracted
} }

View File

@@ -35,8 +35,7 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
yml, _ := mt.Render() yml, _ := mt.Render()
// the rendered output should be a ref to the media type. // the rendered output should be a ref to the media type.
op := `schema: op := `schema: {"$ref": "#/components/schemas/Pet"}`
$ref: '#/components/schemas/Pet'`
assert.Equal(t, op, strings.TrimSpace(string(yml))) assert.Equal(t, op, strings.TrimSpace(string(yml)))
@@ -45,30 +44,30 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
op = `schema: op = `schema:
required: required:
- name - "name"
- photoUrls - "photoUrls"
type: "object" type: "object"
properties: properties:
id: "id":
type: "integer" type: "integer"
format: "int64" format: "int64"
example: 10 example: 10
name: "name":
type: "string" type: "string"
example: "doggie" example: "doggie"
category: "category":
type: "object" type: "object"
properties: properties:
id: "id":
type: "integer" type: "integer"
format: "int64" format: "int64"
example: 1 example: 1
name: "name":
type: "string" type: "string"
example: "Dogs" example: "Dogs"
xml: xml:
name: "category" name: "category"
photoUrls: "photoUrls":
type: "array" type: "array"
xml: xml:
wrapped: true wrapped: true
@@ -76,27 +75,27 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
type: "string" type: "string"
xml: xml:
name: "photoUrl" name: "photoUrl"
tags: "tags":
type: "array" type: "array"
xml: xml:
wrapped: true wrapped: true
items: items:
type: "object" type: "object"
properties: properties:
id: "id":
type: "integer" type: "integer"
format: "int64" format: "int64"
name: "name":
type: "string" type: "string"
xml: xml:
name: "tag" name: "tag"
status: "status":
type: "string" type: "string"
description: "pet status in the store" description: "pet status in the store"
enum: enum:
- available - "available"
- pending - "pending"
- sold - "sold"
xml: xml:
name: "pet" name: "pet"
example: testing a nice mutation` example: testing a nice mutation`
@@ -104,7 +103,6 @@ example: testing a nice mutation`
yml, _ = mt.RenderInline() yml, _ = mt.RenderInline()
assert.Equal(t, op, strings.TrimSpace(string(yml))) assert.Equal(t, op, strings.TrimSpace(string(yml)))
} }
func TestMediaType_MarshalYAML(t *testing.T) { func TestMediaType_MarshalYAML(t *testing.T) {
@@ -125,22 +123,19 @@ func TestMediaType_MarshalYAML(t *testing.T) {
yml, _ := mt.Render() yml, _ := mt.Render()
// the rendered output should be a ref to the media type. // the rendered output should be a ref to the media type.
op := `schema: op := `schema: {"$ref": "#/components/schemas/Pet"}`
$ref: '#/components/schemas/Pet'`
assert.Equal(t, op, strings.TrimSpace(string(yml))) assert.Equal(t, op, strings.TrimSpace(string(yml)))
// modify the media type to have an example // modify the media type to have an example
mt.Example = "testing a nice mutation" mt.Example = "testing a nice mutation"
op = `schema: op = `schema: {"$ref": "#/components/schemas/Pet"}
$ref: '#/components/schemas/Pet'
example: testing a nice mutation` example: testing a nice mutation`
yml, _ = mt.Render() yml, _ = mt.Render()
assert.Equal(t, op, strings.TrimSpace(string(yml))) assert.Equal(t, op, strings.TrimSpace(string(yml)))
} }
func TestMediaType_Examples(t *testing.T) { func TestMediaType_Examples(t *testing.T) {

View File

@@ -16,8 +16,8 @@ type OAuthFlow struct {
AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshUrl string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` RefreshUrl string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes orderedmap.Map[string, string] `json:"scopes,omitempty" yaml:"scopes,omitempty"` Scopes *orderedmap.Map[string, string] `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.OAuthFlow low *low.OAuthFlow
} }
@@ -29,7 +29,7 @@ func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow {
o.AuthorizationUrl = flow.AuthorizationUrl.Value o.AuthorizationUrl = flow.AuthorizationUrl.Value
o.RefreshUrl = flow.RefreshUrl.Value o.RefreshUrl = flow.RefreshUrl.Value
scopes := orderedmap.New[string, string]() scopes := orderedmap.New[string, string]()
for pair := flow.Scopes.Value.First(); pair != nil; pair = pair.Next() { for pair := orderedmap.First(flow.Scopes.Value); pair != nil; pair = pair.Next() {
scopes.Set(pair.Key().Value, pair.Value().Value) scopes.Set(pair.Key().Value, pair.Value().Value)
} }
o.Scopes = scopes o.Scopes = scopes

View File

@@ -8,7 +8,9 @@ import (
"testing" "testing"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
) )
func TestOAuthFlow_MarshalYAML(t *testing.T) { func TestOAuthFlow_MarshalYAML(t *testing.T) {
@@ -37,7 +39,9 @@ scopes:
// mutate // mutate
oflow.Scopes = nil oflow.Scopes = nil
oflow.Extensions = map[string]interface{}{"x-burgers": "why not?"} ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
oflow.Extensions = ext
desired = `authorizationUrl: https://pb33f.io desired = `authorizationUrl: https://pb33f.io
tokenUrl: https://pb33f.io/token tokenUrl: https://pb33f.io/token

View File

@@ -6,6 +6,7 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -16,7 +17,7 @@ type OAuthFlows struct {
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"` ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"` AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.OAuthFlows low *low.OAuthFlows
} }

View File

@@ -25,11 +25,11 @@ type Operation struct {
Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
Responses *Responses `json:"responses,omitempty" yaml:"responses,omitempty"` Responses *Responses `json:"responses,omitempty" yaml:"responses,omitempty"`
Callbacks orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` Callbacks *orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"` Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"`
Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"` Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Operation low *low.Operation
} }
@@ -45,7 +45,9 @@ func NewOperation(operation *low.Operation) *Operation {
} }
o.Tags = tags o.Tags = tags
o.Summary = operation.Summary.Value o.Summary = operation.Summary.Value
if !operation.Deprecated.IsEmpty() {
o.Deprecated = &operation.Deprecated.Value o.Deprecated = &operation.Deprecated.Value
}
o.Description = operation.Description.Value o.Description = operation.Description.Value
if !operation.ExternalDocs.IsEmpty() { if !operation.ExternalDocs.IsEmpty() {
o.ExternalDocs = base.NewExternalDoc(operation.ExternalDocs.Value) o.ExternalDocs = base.NewExternalDoc(operation.ExternalDocs.Value)

View File

@@ -50,13 +50,13 @@ callbacks:
assert.Equal(t, "https://pb33f.io", r.ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", r.ExternalDocs.URL)
assert.Equal(t, 1, r.GoLow().ExternalDocs.KeyNode.Line) assert.Equal(t, 1, r.GoLow().ExternalDocs.KeyNode.Line)
assert.Contains(t, r.Callbacks, "testCallback")
assert.Contains(t, r.Callbacks.GetOrZero("testCallback").Expression, "{$request.body#/callbackUrl}") assert.NotNil(t, r.Callbacks.GetOrZero("testCallback"))
assert.NotNil(t, r.Callbacks.GetOrZero("testCallback").Expression.GetOrZero("{$request.body#/callbackUrl}"))
assert.Equal(t, 3, r.GoLow().Callbacks.KeyNode.Line) assert.Equal(t, 3, r.GoLow().Callbacks.KeyNode.Line)
} }
func TestOperation_MarshalYAML(t *testing.T) { func TestOperation_MarshalYAML(t *testing.T) {
op := &Operation{ op := &Operation{
Tags: []string{"test"}, Tags: []string{"test"},
Summary: "nice", Summary: "nice",
@@ -90,11 +90,9 @@ requestBody:
description: dice` description: dice`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }
func TestOperation_MarshalYAMLInline(t *testing.T) { func TestOperation_MarshalYAMLInline(t *testing.T) {
op := &Operation{ op := &Operation{
Tags: []string{"test"}, Tags: []string{"test"},
Summary: "nice", Summary: "nice",
@@ -128,7 +126,6 @@ requestBody:
description: dice` description: dice`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }
func TestOperation_EmptySecurity(t *testing.T) { func TestOperation_EmptySecurity(t *testing.T) {
@@ -147,7 +144,6 @@ security: []`
assert.NotNil(t, r.Security) assert.NotNil(t, r.Security)
assert.Len(t, r.Security, 0) assert.Len(t, r.Security, 0)
} }
func TestOperation_NoSecurity(t *testing.T) { func TestOperation_NoSecurity(t *testing.T) {
@@ -164,5 +160,4 @@ func TestOperation_NoSecurity(t *testing.T) {
r := NewOperation(&n) r := NewOperation(&n)
assert.Nil(t, r.Security) assert.Nil(t, r.Security)
} }

View File

@@ -19,17 +19,17 @@ type Parameter struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"` In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"` Required *bool `json:"required,renderZero,omitempty" yaml:"required,renderZero,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"` Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` Explode *bool `json:"explode,renderZero,omitempty" yaml:"explode,renderZero,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"` Example *yaml.Node `json:"example,omitempty" yaml:"example,omitempty"`
Examples orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` Examples *orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Parameter low *low.Parameter
} }
@@ -50,7 +50,9 @@ func NewParameter(param *low.Parameter) *Parameter {
if !param.Schema.IsEmpty() { if !param.Schema.IsEmpty() {
p.Schema = base.NewSchemaProxy(&param.Schema) p.Schema = base.NewSchemaProxy(&param.Schema)
} }
p.Required = param.Required.Value if !param.Required.IsEmpty() {
p.Required = &param.Required.Value
}
p.Example = param.Example.Value p.Example = param.Example.Value
p.Examples = base.ExtractExamples(param.Examples.Value) p.Examples = base.ExtractExamples(param.Examples.Value)
p.Content = ExtractContent(param.Content.Value) p.Content = ExtractContent(param.Content.Value)

View File

@@ -9,10 +9,14 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
) )
func TestParameter_MarshalYAML(t *testing.T) { func TestParameter_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
explode := true explode := true
param := Parameter{ param := Parameter{
@@ -23,11 +27,11 @@ func TestParameter_MarshalYAML(t *testing.T) {
Style: "simple", Style: "simple",
Explode: &explode, Explode: &explode,
AllowReserved: true, AllowReserved: true,
Example: "example", Example: utils.CreateStringNode("example"),
Examples: orderedmap.ToOrderedMap(map[string]*base.Example{ Examples: orderedmap.ToOrderedMap(map[string]*base.Example{
"example": {Value: "example"}, "example": {Value: utils.CreateStringNode("example")},
}), }),
Extensions: map[string]interface{}{"x-burgers": "why not?"}, Extensions: ext,
} }
rend, _ := param.Render() rend, _ := param.Render()
@@ -49,6 +53,8 @@ x-burgers: why not?`
} }
func TestParameter_MarshalYAMLInline(t *testing.T) { func TestParameter_MarshalYAMLInline(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
explode := true explode := true
param := Parameter{ param := Parameter{
@@ -59,11 +65,11 @@ func TestParameter_MarshalYAMLInline(t *testing.T) {
Style: "simple", Style: "simple",
Explode: &explode, Explode: &explode,
AllowReserved: true, AllowReserved: true,
Example: "example", Example: utils.CreateStringNode("example"),
Examples: orderedmap.ToOrderedMap(map[string]*base.Example{ Examples: orderedmap.ToOrderedMap(map[string]*base.Example{
"example": {Value: "example"}, "example": {Value: utils.CreateStringNode("example")},
}), }),
Extensions: map[string]interface{}{"x-burgers": "why not?"}, Extensions: ext,
} }
rend, _ := param.RenderInline() rend, _ := param.RenderInline()
@@ -85,7 +91,6 @@ x-burgers: why not?`
} }
func TestParameter_IsExploded(t *testing.T) { func TestParameter_IsExploded(t *testing.T) {
explode := true explode := true
param := Parameter{ param := Parameter{
Explode: &explode, Explode: &explode,
@@ -106,7 +111,6 @@ func TestParameter_IsExploded(t *testing.T) {
} }
func TestParameter_IsDefaultFormEncoding(t *testing.T) { func TestParameter_IsDefaultFormEncoding(t *testing.T) {
param := Parameter{} param := Parameter{}
assert.True(t, param.IsDefaultFormEncoding()) assert.True(t, param.IsDefaultFormEncoding())
@@ -133,7 +137,6 @@ func TestParameter_IsDefaultFormEncoding(t *testing.T) {
} }
func TestParameter_IsDefaultHeaderEncoding(t *testing.T) { func TestParameter_IsDefaultHeaderEncoding(t *testing.T) {
param := Parameter{} param := Parameter{}
assert.True(t, param.IsDefaultHeaderEncoding()) assert.True(t, param.IsDefaultHeaderEncoding())
@@ -163,8 +166,6 @@ func TestParameter_IsDefaultHeaderEncoding(t *testing.T) {
} }
func TestParameter_IsDefaultPathEncoding(t *testing.T) { func TestParameter_IsDefaultPathEncoding(t *testing.T) {
param := Parameter{} param := Parameter{}
assert.True(t, param.IsDefaultPathEncoding()) assert.True(t, param.IsDefaultPathEncoding())
} }

View File

@@ -4,8 +4,13 @@
package v3 package v3
import ( import (
"reflect"
"slices"
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low"
lowV3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -39,12 +44,12 @@ type PathItem struct {
Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"` Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"` Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"`
Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.PathItem low *lowV3.PathItem
} }
// NewPathItem creates a new high-level PathItem instance from a low-level one. // NewPathItem creates a new high-level PathItem instance from a low-level one.
func NewPathItem(pathItem *low.PathItem) *PathItem { func NewPathItem(pathItem *lowV3.PathItem) *PathItem {
pi := new(PathItem) pi := new(PathItem)
pi.low = pathItem pi.low = pathItem
pi.Description = pathItem.Description.Value pi.Description = pathItem.Description.Value
@@ -62,7 +67,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
op *Operation op *Operation
} }
opChan := make(chan opResult) opChan := make(chan opResult)
var buildOperation = func(method int, op *low.Operation, c chan opResult) { buildOperation := func(method int, op *lowV3.Operation, c chan opResult) {
if op == nil { if op == nil {
c <- opResult{method: method, op: nil} c <- opResult{method: method, op: nil}
return return
@@ -120,7 +125,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
} }
// GoLow returns the low level instance of PathItem, used to build the high-level one. // GoLow returns the low level instance of PathItem, used to build the high-level one.
func (p *PathItem) GoLow() *low.PathItem { func (p *PathItem) GoLow() *lowV3.PathItem {
return p.low return p.low
} }
@@ -129,32 +134,65 @@ func (p *PathItem) GoLowUntyped() any {
return p.low return p.low
} }
func (p *PathItem) GetOperations() map[string]*Operation { func (p *PathItem) GetOperations() *orderedmap.Map[string, *Operation] {
o := make(map[string]*Operation) o := orderedmap.New[string, *Operation]()
// TODO: this is a bit of a hack, but it works for now. We might just want to actually pull the data out of the document as a map and split it into the individual operations
type op struct {
name string
op *Operation
line int
}
getLine := func(field string, idx int) int {
if p.GoLow() == nil {
return idx
}
l, ok := reflect.ValueOf(p.GoLow()).Elem().FieldByName(field).Interface().(low.NodeReference[*lowV3.Operation])
if !ok || l.GetKeyNode() == nil {
return idx
}
return l.GetKeyNode().Line
}
ops := []op{}
if p.Get != nil { if p.Get != nil {
o[low.GetLabel] = p.Get ops = append(ops, op{name: lowV3.GetLabel, op: p.Get, line: getLine("Get", -8)})
} }
if p.Put != nil { if p.Put != nil {
o[low.PutLabel] = p.Put ops = append(ops, op{name: lowV3.PutLabel, op: p.Put, line: getLine("Put", -7)})
} }
if p.Post != nil { if p.Post != nil {
o[low.PostLabel] = p.Post ops = append(ops, op{name: lowV3.PostLabel, op: p.Post, line: getLine("Post", -6)})
} }
if p.Delete != nil { if p.Delete != nil {
o[low.DeleteLabel] = p.Delete ops = append(ops, op{name: lowV3.DeleteLabel, op: p.Delete, line: getLine("Delete", -5)})
} }
if p.Options != nil { if p.Options != nil {
o[low.OptionsLabel] = p.Options ops = append(ops, op{name: lowV3.OptionsLabel, op: p.Options, line: getLine("Options", -4)})
} }
if p.Head != nil { if p.Head != nil {
o[low.HeadLabel] = p.Head ops = append(ops, op{name: lowV3.HeadLabel, op: p.Head, line: getLine("Head", -3)})
} }
if p.Patch != nil { if p.Patch != nil {
o[low.PatchLabel] = p.Patch ops = append(ops, op{name: lowV3.PatchLabel, op: p.Patch, line: getLine("Patch", -2)})
} }
if p.Trace != nil { if p.Trace != nil {
o[low.TraceLabel] = p.Trace ops = append(ops, op{name: lowV3.TraceLabel, op: p.Trace, line: getLine("Trace", -1)})
} }
slices.SortStableFunc(ops, func(a op, b op) int {
return a.line - b.line
})
for _, op := range ops {
o.Set(op.name, op.op)
}
return o return o
} }

View File

@@ -67,11 +67,19 @@ trace:
r := NewPathItem(&n) r := NewPathItem(&n)
assert.Len(t, r.GetOperations(), 8) assert.Equal(t, 8, r.GetOperations().Len())
// test that the operations are in the correct order
expectedOrder := []string{"get", "put", "post", "patch", "delete", "head", "options", "trace"}
i := 0
for pair := r.GetOperations().First(); pair != nil; pair = pair.Next() {
assert.Equal(t, expectedOrder[i], pair.Value().Description)
i++
}
} }
func TestPathItem_MarshalYAML(t *testing.T) { func TestPathItem_MarshalYAML(t *testing.T) {
pi := &PathItem{ pi := &PathItem{
Description: "a path item", Description: "a path item",
Summary: "It's a test, don't worry about it, Jim", Summary: "It's a test, don't worry about it, Jim",
@@ -112,7 +120,6 @@ parameters:
} }
func TestPathItem_MarshalYAMLInline(t *testing.T) { func TestPathItem_MarshalYAMLInline(t *testing.T) {
pi := &PathItem{ pi := &PathItem{
Description: "a path item", Description: "a path item",
Summary: "It's a test, don't worry about it, Jim", Summary: "It's a test, don't worry about it, Jim",

View File

@@ -22,8 +22,8 @@ import (
// constraints. // constraints.
// - https://spec.openapis.org/oas/v3.1.0#paths-object // - https://spec.openapis.org/oas/v3.1.0#paths-object
type Paths struct { type Paths struct {
PathItems orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"` PathItems *orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *v3low.Paths low *v3low.Paths
} }
@@ -81,6 +81,7 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
pi *PathItem pi *PathItem
path string path string
line int line int
style yaml.Style
rendered *yaml.Node rendered *yaml.Node
} }
var mapped []*pathItem var mapped []*pathItem
@@ -89,13 +90,21 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
k := pair.Key() k := pair.Key()
pi := pair.Value() pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom. ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if p.low != nil { if p.low != nil {
lpi := p.low.FindPath(k) lpi := p.low.FindPath(k)
if lpi != nil { if lpi != nil {
ln = lpi.ValueNode.Line ln = lpi.ValueNode.Line
} }
for pair := orderedmap.First(p.low.PathItems); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
} }
mapped = append(mapped, &pathItem{pi, k, ln, nil}) }
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
} }
nb := high.NewNodeBuilder(p, p.low) nb := high.NewNodeBuilder(p, p.low)
@@ -107,23 +116,29 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
label = extNode.Content[u].Value label = extNode.Content[u].Value
continue continue
} }
mapped = append(mapped, &pathItem{nil, label, mapped = append(mapped, &pathItem{
extNode.Content[u].Line, extNode.Content[u]}) nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
} }
} }
sort.Slice(mapped, func(i, j int) bool { sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line return mapped[i].line < mapped[j].line
}) })
for j := range mapped { for _, mp := range mapped {
if mapped[j].pi != nil { if mp.pi != nil {
rendered, _ := mapped[j].pi.MarshalYAML() rendered, _ := mp.pi.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node)) m.Content = append(m.Content, rendered.(*yaml.Node))
} }
if mapped[j].rendered != nil { if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path)) m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mapped[j].rendered) m.Content = append(m.Content, mp.rendered)
} }
} }
@@ -137,6 +152,7 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
pi *PathItem pi *PathItem
path string path string
line int line int
style yaml.Style
rendered *yaml.Node rendered *yaml.Node
} }
var mapped []*pathItem var mapped []*pathItem
@@ -145,13 +161,21 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
k := pair.Key() k := pair.Key()
pi := pair.Value() pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom. ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if p.low != nil { if p.low != nil {
lpi := p.low.FindPath(k) lpi := p.low.FindPath(k)
if lpi != nil { if lpi != nil {
ln = lpi.ValueNode.Line ln = lpi.ValueNode.Line
} }
for pair := orderedmap.First(p.low.PathItems); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
} }
mapped = append(mapped, &pathItem{pi, k, ln, nil}) }
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
} }
nb := high.NewNodeBuilder(p, p.low) nb := high.NewNodeBuilder(p, p.low)
@@ -164,23 +188,29 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
label = extNode.Content[u].Value label = extNode.Content[u].Value
continue continue
} }
mapped = append(mapped, &pathItem{nil, label, mapped = append(mapped, &pathItem{
extNode.Content[u].Line, extNode.Content[u]}) nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
} }
} }
sort.Slice(mapped, func(i, j int) bool { sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line return mapped[i].line < mapped[j].line
}) })
for j := range mapped { for _, mp := range mapped {
if mapped[j].pi != nil { if mp.pi != nil {
rendered, _ := mapped[j].pi.MarshalYAMLInline() rendered, _ := mp.pi.MarshalYAMLInline()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node)) m.Content = append(m.Content, rendered.(*yaml.Node))
} }
if mapped[j].rendered != nil { if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path)) m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mapped[j].rendered) m.Content = append(m.Content, mp.rendered)
} }
} }

View File

@@ -14,9 +14,9 @@ import (
// - https://spec.openapis.org/oas/v3.1.0#request-body-object // - https://spec.openapis.org/oas/v3.1.0#request-body-object
type RequestBody struct { type RequestBody struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Required *bool `json:"required,omitempty" yaml:"required,renderZero,omitempty"` Required *bool `json:"required,omitempty" yaml:"required,renderZero,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.RequestBody low *low.RequestBody
} }
@@ -25,7 +25,7 @@ func NewRequestBody(rb *low.RequestBody) *RequestBody {
r := new(RequestBody) r := new(RequestBody)
r.low = rb r.low = rb
r.Description = rb.Description.Value r.Description = rb.Description.Value
if rb.Required.ValueNode != nil { if !rb.Required.IsEmpty() {
r.Required = &rb.Required.Value r.Required = &rb.Required.Value
} }
r.Extensions = high.ExtractExtensions(rb.Extensions) r.Extensions = high.ExtractExtensions(rb.Extensions)

View File

@@ -7,16 +7,21 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
) )
func TestRequestBody_MarshalYAML(t *testing.T) { func TestRequestBody_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
rb := true rb := true
req := &RequestBody{ req := &RequestBody{
Description: "beer", Description: "beer",
Required: &rb, Required: &rb,
Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, Extensions: ext,
} }
rend, _ := req.Render() rend, _ := req.Render()
@@ -26,16 +31,17 @@ required: true
x-high-gravity: why not?` x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }
func TestRequestBody_MarshalYAMLInline(t *testing.T) { func TestRequestBody_MarshalYAMLInline(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
rb := true rb := true
req := &RequestBody{ req := &RequestBody{
Description: "beer", Description: "beer",
Required: &rb, Required: &rb,
Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, Extensions: ext,
} }
rend, _ := req.RenderInline() rend, _ := req.RenderInline()
@@ -45,15 +51,17 @@ required: true
x-high-gravity: why not?` x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }
func TestRequestBody_MarshalNoRequired(t *testing.T) { func TestRequestBody_MarshalNoRequired(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
rb := false rb := false
req := &RequestBody{ req := &RequestBody{
Description: "beer", Description: "beer",
Required: &rb, Required: &rb,
Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, Extensions: ext,
} }
rend, _ := req.Render() rend, _ := req.Render()
@@ -63,14 +71,15 @@ required: false
x-high-gravity: why not?` x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }
func TestRequestBody_MarshalRequiredNil(t *testing.T) { func TestRequestBody_MarshalRequiredNil(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
req := &RequestBody{ req := &RequestBody{
Description: "beer", Description: "beer",
Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, Extensions: ext,
} }
rend, _ := req.Render() rend, _ := req.Render()
@@ -79,5 +88,4 @@ func TestRequestBody_MarshalRequiredNil(t *testing.T) {
x-high-gravity: why not?` x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }

View File

@@ -17,10 +17,10 @@ import (
// - https://spec.openapis.org/oas/v3.1.0#response-object // - https://spec.openapis.org/oas/v3.1.0#response-object
type Response struct { type Response struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"` Headers *orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Links orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"` Links *orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Response low *low.Response
} }

View File

@@ -21,7 +21,6 @@ import (
// with hard coded line and column numbers in them, changing the spec above the bottom will // with hard coded line and column numbers in them, changing the spec above the bottom will
// create pointless test changes. So here is a standalone test. you know... for science. // create pointless test changes. So here is a standalone test. you know... for science.
func TestNewResponse(t *testing.T) { func TestNewResponse(t *testing.T) {
yml := `description: this is a response yml := `description: this is a response
headers: headers:
someHeader: someHeader:
@@ -46,14 +45,16 @@ links:
assert.Equal(t, 1, orderedmap.Len(r.Headers)) assert.Equal(t, 1, orderedmap.Len(r.Headers))
assert.Equal(t, 1, orderedmap.Len(r.Content)) assert.Equal(t, 1, orderedmap.Len(r.Content))
assert.Equal(t, "pizza!", r.Extensions["x-pizza-man"])
var xPizzaMan string
_ = r.Extensions.GetOrZero("x-pizza-man").Decode(&xPizzaMan)
assert.Equal(t, "pizza!", xPizzaMan)
assert.Equal(t, 1, orderedmap.Len(r.Links)) assert.Equal(t, 1, orderedmap.Len(r.Links))
assert.Equal(t, 1, r.GoLow().Description.KeyNode.Line) assert.Equal(t, 1, r.GoLow().Description.KeyNode.Line)
} }
func TestResponse_MarshalYAML(t *testing.T) { func TestResponse_MarshalYAML(t *testing.T) {
yml := `description: this is a response yml := `description: this is a response
headers: headers:
someHeader: someHeader:
@@ -77,11 +78,9 @@ links:
rend, _ := r.Render() rend, _ := r.Render()
assert.Equal(t, yml, strings.TrimSpace(string(rend))) assert.Equal(t, yml, strings.TrimSpace(string(rend)))
} }
func TestResponse_MarshalYAMLInline(t *testing.T) { func TestResponse_MarshalYAMLInline(t *testing.T) {
yml := `description: this is a response yml := `description: this is a response
headers: headers:
someHeader: someHeader:
@@ -105,5 +104,4 @@ links:
rend, _ := r.RenderInline() rend, _ := r.RenderInline()
assert.Equal(t, yml, strings.TrimSpace(string(rend))) assert.Equal(t, yml, strings.TrimSpace(string(rend)))
} }

View File

@@ -31,9 +31,9 @@ import (
// be the response for a successful operation call. // be the response for a successful operation call.
// - https://spec.openapis.org/oas/v3.1.0#responses-object // - https://spec.openapis.org/oas/v3.1.0#responses-object
type Responses struct { type Responses struct {
Codes orderedmap.Map[string, *Response] `json:"-" yaml:"-"` Codes *orderedmap.Map[string, *Response] `json:"-" yaml:"-"`
Default *Response `json:"default,omitempty" yaml:"default,omitempty"` Default *Response `json:"default,omitempty" yaml:"default,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Responses low *low.Responses
} }
@@ -97,19 +97,22 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
code string code string
line int line int
ext *yaml.Node ext *yaml.Node
style yaml.Style
} }
var mapped []*responseItem var mapped []*responseItem
for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() {
ln := 9999 // default to a high value to weight new content to the bottom. ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if r.low != nil { if r.low != nil {
for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() { for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() {
if lPair.Key().Value == pair.Key() { if lPair.Key().Value == pair.Key() {
ln = lPair.Key().KeyNode.Line ln = lPair.Key().KeyNode.Line
style = lPair.Key().KeyNode.Style
} }
} }
} }
mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil}) mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil, style})
} }
// extract extensions // extract extensions
@@ -124,7 +127,7 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
} }
mapped = append(mapped, &responseItem{ mapped = append(mapped, &responseItem{
nil, label, nil, label,
extNode.Content[u].Line, extNode.Content[u], extNode.Content[u].Line, extNode.Content[u], 0,
}) })
} }
} }
@@ -132,15 +135,19 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
sort.Slice(mapped, func(i, j int) bool { sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line return mapped[i].line < mapped[j].line
}) })
for j := range mapped { for _, mp := range mapped {
if mapped[j].resp != nil { if mp.resp != nil {
rendered, _ := mapped[j].resp.MarshalYAML() rendered, _ := mp.resp.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
kn := utils.CreateStringNode(mp.code)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node)) m.Content = append(m.Content, rendered.(*yaml.Node))
} }
if mapped[j].ext != nil { if mp.ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code)) m.Content = append(m.Content, utils.CreateStringNode(mp.code))
m.Content = append(m.Content, mapped[j].ext) m.Content = append(m.Content, mp.ext)
} }
} }
@@ -155,19 +162,22 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
code string code string
line int line int
ext *yaml.Node ext *yaml.Node
style yaml.Style
} }
var mapped []*responseItem var mapped []*responseItem
for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() {
ln := 9999 // default to a high value to weight new content to the bottom. ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if r.low != nil { if r.low != nil {
for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() { for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() {
if lPair.Key().Value == pair.Key() { if lPair.Key().Value == pair.Key() {
ln = lPair.Key().KeyNode.Line ln = lPair.Key().KeyNode.Line
style = lPair.Key().KeyNode.Style
} }
} }
} }
mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil}) mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil, style})
} }
// extract extensions // extract extensions
@@ -183,7 +193,7 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
} }
mapped = append(mapped, &responseItem{ mapped = append(mapped, &responseItem{
nil, label, nil, label,
extNode.Content[u].Line, extNode.Content[u], extNode.Content[u].Line, extNode.Content[u], 0,
}) })
} }
} }
@@ -191,16 +201,20 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
sort.Slice(mapped, func(i, j int) bool { sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line return mapped[i].line < mapped[j].line
}) })
for j := range mapped { for _, mp := range mapped {
if mapped[j].resp != nil { if mp.resp != nil {
rendered, _ := mapped[j].resp.MarshalYAMLInline() rendered, _ := mp.resp.MarshalYAMLInline()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
kn := utils.CreateStringNode(mp.code)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node)) m.Content = append(m.Content, rendered.(*yaml.Node))
} }
if mapped[j].ext != nil { if mp.ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code)) m.Content = append(m.Content, utils.CreateStringNode(mp.code))
m.Content = append(m.Content, mapped[j].ext) m.Content = append(m.Content, mp.ext)
} }
} }

View File

@@ -6,6 +6,7 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -28,7 +29,7 @@ type SecurityScheme struct {
BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"` BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"` Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"` OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.SecurityScheme low *low.SecurityScheme
} }

View File

@@ -15,8 +15,8 @@ import (
type Server struct { type Server struct {
URL string `json:"url,omitempty" yaml:"url,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables orderedmap.Map[string, *ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"` Variables *orderedmap.Map[string, *ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Server low *low.Server
} }

View File

@@ -22,13 +22,13 @@ import (
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName low.NodeReference[string] PropertyName low.NodeReference[string]
Mapping low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] Mapping low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]]
low.Reference low.Reference
} }
// FindMappingValue will return a ValueReference containing the string mapping value // FindMappingValue will return a ValueReference containing the string mapping value
func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] { func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] {
for pair := d.Mapping.Value.First(); pair != nil; pair = pair.Next() { for pair := orderedmap.First(d.Mapping.Value); pair != nil; pair = pair.Next() {
if pair.Key().Value == key { if pair.Key().Value == key {
v := pair.Value() v := pair.Value()
return &v return &v
@@ -45,7 +45,7 @@ func (d *Discriminator) Hash() [32]byte {
f = append(f, d.PropertyName.Value) f = append(f, d.PropertyName.Value)
} }
for pair := orderedmap.First(d.Mapping.Value); pair != nil; pair = pair.Next() { for pair := orderedmap.First(orderedmap.SortAlpha(d.Mapping.Value)); pair != nil; pair = pair.Next() {
f = append(f, pair.Value().Value) f = append(f, pair.Value().Value)
} }

View File

@@ -7,12 +7,11 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort"
"strconv"
"strings" "strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -23,15 +22,15 @@ import (
type Example struct { type Example struct {
Summary low.NodeReference[string] Summary low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
Value low.NodeReference[any] Value low.NodeReference[*yaml.Node]
ExternalValue low.NodeReference[string] ExternalValue low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference *low.Reference
} }
// FindExtension returns a ValueReference containing the extension value, if found. // FindExtension returns a ValueReference containing the extension value, if found.
func (ex *Example) FindExtension(ext string) *low.ValueReference[any] { func (ex *Example) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, ex.Extensions) return low.FindItemInOrderedMap[*yaml.Node](ext, ex.Extensions)
} }
// Hash will return a consistent SHA256 Hash of the Discriminator object // Hash will return a consistent SHA256 Hash of the Discriminator object
@@ -43,21 +42,15 @@ func (ex *Example) Hash() [32]byte {
if ex.Description.Value != "" { if ex.Description.Value != "" {
f = append(f, ex.Description.Value) f = append(f, ex.Description.Value)
} }
if ex.Value.Value != "" { if ex.Value.Value != nil && !ex.Value.Value.IsZero() {
// this could be anything! // this could be anything!
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(ex.Value.Value))))) b, _ := yaml.Marshal(ex.Value.Value)
f = append(f, fmt.Sprintf("%x", sha256.Sum256(b)))
} }
if ex.ExternalValue.Value != "" { if ex.ExternalValue.Value != "" {
f = append(f, ex.ExternalValue.Value) f = append(f, ex.ExternalValue.Value)
} }
keys := make([]string, len(ex.Extensions)) f = append(f, low.HashExtensions(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, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }
@@ -70,32 +63,8 @@ func (ex *Example) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
_, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content)
if vn != nil { if vn != nil {
var n map[string]interface{} ex.Value = low.NodeReference[*yaml.Node]{
err := vn.Decode(&n) Value: vn,
if err != nil {
// if not a map, then try an array
var k []interface{}
err = vn.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = vn.Decode(&j)
ex.Value = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
ex.Value = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
ex.Value = low.NodeReference[any]{
Value: n,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
@@ -105,33 +74,6 @@ func (ex *Example) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
} }
// GetExtensions will return Example extensions to satisfy the HasExtensions interface. // GetExtensions will return Example extensions to satisfy the HasExtensions interface.
func (ex *Example) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (ex *Example) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return ex.Extensions return ex.Extensions
} }
// ExtractExampleValue will extract a primitive example value (if possible), or just the raw Value property if not.
func ExtractExampleValue(exp *yaml.Node) any {
if utils.IsNodeBoolValue(exp) {
v, _ := strconv.ParseBool(exp.Value)
return v
}
if utils.IsNodeIntValue(exp) {
v, _ := strconv.ParseInt(exp.Value, 10, 64)
return v
}
if utils.IsNodeFloatValue(exp) {
v, _ := strconv.ParseFloat(exp.Value, 64)
return v
}
if utils.IsNodeMap(exp) {
var m map[string]interface{}
_ = exp.Decode(&m)
return m
}
if utils.IsNodeArray(exp) {
var m []interface{}
_ = exp.Decode(&m)
return m
}
return exp.Value
}

View File

@@ -5,15 +5,17 @@ package base
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestExample_Build_Success_NoValue(t *testing.T) { func TestExample_Build_Success_NoValue(t *testing.T) {
yml := `summary: hot yml := `summary: hot
description: cakes description: cakes
x-cake: hot` x-cake: hot`
@@ -32,13 +34,13 @@ x-cake: hot`
assert.Equal(t, "hot", n.Summary.Value) assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value) assert.Equal(t, "cakes", n.Description.Value)
assert.Nil(t, n.Value.Value) assert.Nil(t, n.Value.Value)
ext := n.FindExtension("x-cake")
assert.NotNil(t, ext) var xCake string
assert.Equal(t, "hot", ext.Value) _ = n.FindExtension("x-cake").Value.Decode(&xCake)
assert.Equal(t, "hot", xCake)
} }
func TestExample_Build_Success_Simple(t *testing.T) { func TestExample_Build_Success_Simple(t *testing.T) {
yml := `summary: hot yml := `summary: hot
description: cakes description: cakes
value: a string example value: a string example
@@ -57,14 +59,17 @@ x-cake: hot`
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value) assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value) assert.Equal(t, "cakes", n.Description.Value)
assert.Equal(t, "a string example", n.Value.Value)
ext := n.FindExtension("x-cake") var example string
assert.NotNil(t, ext) err = n.Value.Value.Decode(&example)
assert.Equal(t, "hot", ext.Value) assert.Equal(t, "a string example", example)
var xCake string
_ = n.FindExtension("x-cake").Value.Decode(&xCake)
assert.Equal(t, "hot", xCake)
} }
func TestExample_Build_Success_Object(t *testing.T) { func TestExample_Build_Success_Object(t *testing.T) {
yml := `summary: hot yml := `summary: hot
description: cakes description: cakes
value: value:
@@ -85,17 +90,15 @@ value:
assert.Equal(t, "hot", n.Summary.Value) assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value) assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.(map[string]interface{}); ok { var m map[string]interface{}
assert.Equal(t, "oven", v["pizza"]) err = n.Value.Value.Decode(&m)
assert.Equal(t, "pizza", v["yummy"]) require.NoError(t, err)
} else {
assert.Fail(t, "failed to decode correctly.")
}
assert.Equal(t, "oven", m["pizza"])
assert.Equal(t, "pizza", m["yummy"])
} }
func TestExample_Build_Success_Array(t *testing.T) { func TestExample_Build_Success_Array(t *testing.T) {
yml := `summary: hot yml := `summary: hot
description: cakes description: cakes
value: value:
@@ -116,16 +119,15 @@ value:
assert.Equal(t, "hot", n.Summary.Value) assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value) assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.([]interface{}); ok { var a []any
assert.Equal(t, "wow", v[0]) err = n.Value.Value.Decode(&a)
assert.Equal(t, "such array", v[1]) require.NoError(t, err)
} else {
assert.Fail(t, "failed to decode correctly.") assert.Equal(t, "wow", a[0])
} assert.Equal(t, "such array", a[1])
} }
func TestExample_Build_Success_MergeNode(t *testing.T) { func TestExample_Build_Success_MergeNode(t *testing.T) {
yml := `x-things: &things yml := `x-things: &things
summary: hot summary: hot
description: cakes description: cakes
@@ -148,71 +150,15 @@ func TestExample_Build_Success_MergeNode(t *testing.T) {
assert.Equal(t, "hot", n.Summary.Value) assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value) assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.([]interface{}); ok { var a []any
assert.Equal(t, "wow", v[0]) err = n.Value.GetValue().Decode(&a)
assert.Equal(t, "such array", v[1]) require.NoError(t, err)
} else {
assert.Fail(t, "failed to decode correctly.")
}
} assert.Equal(t, "wow", a[0])
assert.Equal(t, "such array", a[1])
func TestExample_ExtractExampleValue_Map(t *testing.T) {
yml := `hot:
summer: nights
pizza: oven`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
val := ExtractExampleValue(idxNode.Content[0])
if v, ok := val.(map[string]interface{}); ok {
if r, rok := v["hot"].(map[string]interface{}); rok {
assert.Equal(t, "nights", r["summer"])
assert.Equal(t, "oven", r["pizza"])
} else {
assert.Fail(t, "failed to decode correctly.")
}
} else {
assert.Fail(t, "failed to decode correctly.")
}
}
func TestExample_ExtractExampleValue_Slice(t *testing.T) {
yml := `- hot:
summer: nights
- hotter:
pizza: oven`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
val := ExtractExampleValue(idxNode.Content[0])
if v, ok := val.([]interface{}); ok {
for w := range v {
if r, rok := v[w].(map[string]interface{}); rok {
for k := range r {
if k == "hotter" {
assert.Equal(t, "oven", r[k].(map[string]interface{})["pizza"])
}
if k == "hot" {
assert.Equal(t, "nights", r[k].(map[string]interface{})["summer"])
}
}
} else {
assert.Fail(t, "failed to decode correctly.")
}
}
} else {
assert.Fail(t, "failed to decode correctly.")
}
} }
func TestExample_Hash(t *testing.T) { func TestExample_Hash(t *testing.T) {
left := `summary: hot left := `summary: hot
description: cakes description: cakes
x-burger: nice x-burger: nice
@@ -242,13 +188,5 @@ x-burger: nice`
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil) _ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash()) assert.Equal(t, lDoc.Hash(), rDoc.Hash())
assert.Len(t, lDoc.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(lDoc.GetExtensions()))
}
func TestExtractExampleValue(t *testing.T) {
assert.True(t, ExtractExampleValue(&yaml.Node{Tag: "!!bool", Value: "true"}).(bool))
assert.Equal(t, int64(10), ExtractExampleValue(&yaml.Node{Tag: "!!int", Value: "10"}).(int64))
assert.Equal(t, 33.2, ExtractExampleValue(&yaml.Node{Tag: "!!float", Value: "33.2"}).(float64))
assert.Equal(t, "WHAT A NICE COW", ExtractExampleValue(&yaml.Node{Tag: "!!str", Value: "WHAT A NICE COW"}))
} }

View File

@@ -6,13 +6,13 @@ package base
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort"
"strings"
) )
// ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3
@@ -24,13 +24,13 @@ import (
type ExternalDoc struct { type ExternalDoc struct {
Description low.NodeReference[string] Description low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference *low.Reference
} }
// FindExtension returns a ValueReference containing the extension value, if found. // FindExtension returns a ValueReference containing the extension value, if found.
func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] { func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, ex.Extensions) return low.FindItemInOrderedMap[*yaml.Node](ext, ex.Extensions)
} }
// Build will extract extensions from the ExternalDoc instance. // Build will extract extensions from the ExternalDoc instance.
@@ -43,7 +43,7 @@ func (ex *ExternalDoc) Build(_ context.Context, _, root *yaml.Node, idx *index.S
} }
// GetExtensions returns all ExternalDoc extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all ExternalDoc extensions and satisfies the low.HasExtensions interface.
func (ex *ExternalDoc) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (ex *ExternalDoc) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return ex.Extensions return ex.Extensions
} }
@@ -53,13 +53,6 @@ func (ex *ExternalDoc) Hash() [32]byte {
ex.Description.Value, ex.Description.Value,
ex.URL.Value, ex.URL.Value,
} }
keys := make([]string, len(ex.Extensions)) f = append(f, low.HashExtensions(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, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -5,15 +5,16 @@ package base
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestExternalDoc_FindExtension(t *testing.T) { func TestExternalDoc_FindExtension(t *testing.T) {
yml := `x-fish: cake` yml := `x-fish: cake`
var idxNode yaml.Node var idxNode yaml.Node
@@ -26,12 +27,14 @@ func TestExternalDoc_FindExtension(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "cake", n.FindExtension("x-fish").Value)
var xFish string
_ = n.FindExtension("x-fish").Value.Decode(&xFish)
assert.Equal(t, "cake", xFish)
} }
func TestExternalDoc_Build(t *testing.T) { func TestExternalDoc_Build(t *testing.T) {
yml := `url: https://pb33f.io yml := `url: https://pb33f.io
description: the ranch description: the ranch
x-b33f: princess` x-b33f: princess`
@@ -49,14 +52,13 @@ x-b33f: princess`
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "https://pb33f.io", n.URL.Value) assert.Equal(t, "https://pb33f.io", n.URL.Value)
assert.Equal(t, "the ranch", n.Description.Value) assert.Equal(t, "the ranch", n.Description.Value)
ext := n.FindExtension("x-b33f")
assert.NotNil(t, ext)
assert.Equal(t, "princess", ext.Value)
var xB33f string
_ = n.FindExtension("x-b33f").Value.Decode(&xB33f)
assert.Equal(t, "princess", xB33f)
} }
func TestExternalDoc_Hash(t *testing.T) { func TestExternalDoc_Hash(t *testing.T) {
left := `url: https://pb33f.io left := `url: https://pb33f.io
description: the ranch description: the ranch
x-b33f: princess` x-b33f: princess`
@@ -78,5 +80,5 @@ description: the ranch`
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil) _ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash()) assert.Equal(t, lDoc.Hash(), rDoc.Hash())
assert.Len(t, lDoc.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(lDoc.GetExtensions()))
} }

View File

@@ -6,11 +6,11 @@ package base
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/utils"
"sort"
"strings" "strings"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -31,17 +31,17 @@ type Info struct {
Contact low.NodeReference[*Contact] Contact low.NodeReference[*Contact]
License low.NodeReference[*License] License low.NodeReference[*License]
Version low.NodeReference[string] Version low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference *low.Reference
} }
// FindExtension attempts to locate an extension with the supplied key // FindExtension attempts to locate an extension with the supplied key
func (i *Info) FindExtension(ext string) *low.ValueReference[any] { func (i *Info) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap(ext, i.Extensions) return low.FindItemInOrderedMap(ext, i.Extensions)
} }
// GetExtensions returns all extensions for Info // GetExtensions returns all extensions for Info
func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (i *Info) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return i.Extensions return i.Extensions
} }
@@ -87,13 +87,6 @@ func (i *Info) Hash() [32]byte {
if !i.Version.IsEmpty() { if !i.Version.IsEmpty() {
f = append(f, i.Version.Value) f = append(f, i.Version.Value)
} }
keys := make([]string, len(i.Extensions)) f = append(f, low.HashExtensions(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, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -54,10 +55,11 @@ x-cli-name: pizza cli`
assert.Equal(t, "magic", lic.Name.Value) assert.Equal(t, "magic", lic.Name.Value)
assert.Equal(t, "https://pb33f.io/license", lic.URL.Value) assert.Equal(t, "https://pb33f.io/license", lic.URL.Value)
cliName := n.FindExtension("x-cli-name") var xCliName string
assert.NotNil(t, cliName) _ = n.FindExtension("x-cli-name").Value.Decode(&xCliName)
assert.Equal(t, "pizza cli", cliName.Value)
assert.Len(t, n.GetExtensions(), 1) assert.Equal(t, "pizza cli", xCliName)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }
func TestContact_Build(t *testing.T) { func TestContact_Build(t *testing.T) {

View File

@@ -82,7 +82,7 @@ type Schema struct {
Discriminator low.NodeReference[*Discriminator] Discriminator low.NodeReference[*Discriminator]
// in 3.1 examples can be an array (which is recommended) // in 3.1 examples can be an array (which is recommended)
Examples low.NodeReference[[]low.ValueReference[any]] Examples low.NodeReference[[]low.ValueReference[*yaml.Node]]
// in 3.1 PrefixItems provides tuple validation using prefixItems. // in 3.1 PrefixItems provides tuple validation using prefixItems.
PrefixItems low.NodeReference[[]low.ValueReference[*SchemaProxy]] PrefixItems low.NodeReference[[]low.ValueReference[*SchemaProxy]]
// in 3.1 Contains is used by arrays and points to a Schema. // in 3.1 Contains is used by arrays and points to a Schema.
@@ -97,8 +97,8 @@ type Schema struct {
If low.NodeReference[*SchemaProxy] If low.NodeReference[*SchemaProxy]
Else low.NodeReference[*SchemaProxy] Else low.NodeReference[*SchemaProxy]
Then low.NodeReference[*SchemaProxy] Then low.NodeReference[*SchemaProxy]
DependentSchemas low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]] DependentSchemas low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
PatternProperties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]] PatternProperties low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
PropertyNames low.NodeReference[*SchemaProxy] PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy] UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]] UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
@@ -119,23 +119,23 @@ type Schema struct {
MaxProperties low.NodeReference[int64] MaxProperties low.NodeReference[int64]
MinProperties low.NodeReference[int64] MinProperties low.NodeReference[int64]
Required low.NodeReference[[]low.ValueReference[string]] Required low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[any]] Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
Not low.NodeReference[*SchemaProxy] Not low.NodeReference[*SchemaProxy]
Properties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]] Properties low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
AdditionalProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]] AdditionalProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
Description low.NodeReference[string] Description low.NodeReference[string]
ContentEncoding low.NodeReference[string] ContentEncoding low.NodeReference[string]
ContentMediaType low.NodeReference[string] ContentMediaType low.NodeReference[string]
Default low.NodeReference[any] Default low.NodeReference[*yaml.Node]
Const low.NodeReference[any] Const low.NodeReference[*yaml.Node]
Nullable low.NodeReference[bool] Nullable low.NodeReference[bool]
ReadOnly low.NodeReference[bool] ReadOnly low.NodeReference[bool]
WriteOnly low.NodeReference[bool] WriteOnly low.NodeReference[bool]
XML low.NodeReference[*XML] XML low.NodeReference[*XML]
ExternalDocs low.NodeReference[*ExternalDoc] ExternalDocs low.NodeReference[*ExternalDoc]
Example low.NodeReference[any] Example low.NodeReference[*yaml.Node]
Deprecated low.NodeReference[bool] Deprecated low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. // Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
ParentProxy *SchemaProxy ParentProxy *SchemaProxy
@@ -255,23 +255,13 @@ func (s *Schema) Hash() [32]byte {
keys = make([]string, len(s.Enum.Value)) keys = make([]string, len(s.Enum.Value))
for i := range s.Enum.Value { for i := range s.Enum.Value {
keys[i] = fmt.Sprint(s.Enum.Value[i].Value) keys[i] = low.ValueToString(s.Enum.Value[i].Value)
} }
sort.Strings(keys) sort.Strings(keys)
d = append(d, keys...) d = append(d, keys...)
for i := range s.Enum.Value { for pair := orderedmap.First(orderedmap.SortAlpha(s.Properties.Value)); pair != nil; pair = pair.Next() {
d = append(d, fmt.Sprint(s.Enum.Value[i].Value)) d = append(d, fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)))
}
propKeys := make([]string, orderedmap.Len(s.Properties.Value))
z := 0
for pair := orderedmap.First(s.Properties.Value); pair != nil; pair = pair.Next() {
propKeys[z] = pair.Key().Value
z++
}
sort.Strings(propKeys)
for k := range propKeys {
d = append(d, low.GenerateHashString(s.FindProperty(propKeys[k]).Value))
} }
if s.XML.Value != nil { if s.XML.Value != nil {
d = append(d, low.GenerateHashString(s.XML.Value)) d = append(d, low.GenerateHashString(s.XML.Value))
@@ -287,7 +277,7 @@ func (s *Schema) Hash() [32]byte {
if len(s.OneOf.Value) > 0 { if len(s.OneOf.Value) > 0 {
oneOfKeys := make([]string, len(s.OneOf.Value)) oneOfKeys := make([]string, len(s.OneOf.Value))
oneOfEntities := make(map[string]*SchemaProxy) oneOfEntities := make(map[string]*SchemaProxy)
z = 0 z := 0
for i := range s.OneOf.Value { for i := range s.OneOf.Value {
g := s.OneOf.Value[i].Value g := s.OneOf.Value[i].Value
r := low.GenerateHashString(g) r := low.GenerateHashString(g)
@@ -305,7 +295,7 @@ func (s *Schema) Hash() [32]byte {
if len(s.AllOf.Value) > 0 { if len(s.AllOf.Value) > 0 {
allOfKeys := make([]string, len(s.AllOf.Value)) allOfKeys := make([]string, len(s.AllOf.Value))
allOfEntities := make(map[string]*SchemaProxy) allOfEntities := make(map[string]*SchemaProxy)
z = 0 z := 0
for i := range s.AllOf.Value { for i := range s.AllOf.Value {
g := s.AllOf.Value[i].Value g := s.AllOf.Value[i].Value
r := low.GenerateHashString(g) r := low.GenerateHashString(g)
@@ -323,7 +313,7 @@ func (s *Schema) Hash() [32]byte {
if len(s.AnyOf.Value) > 0 { if len(s.AnyOf.Value) > 0 {
anyOfKeys := make([]string, len(s.AnyOf.Value)) anyOfKeys := make([]string, len(s.AnyOf.Value))
anyOfEntities := make(map[string]*SchemaProxy) anyOfEntities := make(map[string]*SchemaProxy)
z = 0 z := 0
for i := range s.AnyOf.Value { for i := range s.AnyOf.Value {
g := s.AnyOf.Value[i].Value g := s.AnyOf.Value[i].Value
r := low.GenerateHashString(g) r := low.GenerateHashString(g)
@@ -372,32 +362,18 @@ func (s *Schema) Hash() [32]byte {
d = append(d, fmt.Sprint(s.Anchor.Value)) d = append(d, fmt.Sprint(s.Anchor.Value))
} }
depSchemasKeys := make([]string, orderedmap.Len(s.DependentSchemas.Value)) for pair := orderedmap.First(orderedmap.SortAlpha(s.DependentSchemas.Value)); pair != nil; pair = pair.Next() {
z = 0 d = append(d, fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)))
for pair := orderedmap.First(s.DependentSchemas.Value); pair != nil; pair = pair.Next() {
depSchemasKeys[z] = pair.Key().Value
z++
}
sort.Strings(depSchemasKeys)
for k := range depSchemasKeys {
d = append(d, low.GenerateHashString(s.FindDependentSchema(depSchemasKeys[k]).Value))
} }
patternPropsKeys := make([]string, orderedmap.Len(s.PatternProperties.Value)) for pair := orderedmap.First(orderedmap.SortAlpha(s.PatternProperties.Value)); pair != nil; pair = pair.Next() {
z = 0 d = append(d, fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)))
for pair := orderedmap.First(s.PatternProperties.Value); pair != nil; pair = pair.Next() {
patternPropsKeys[z] = pair.Key().Value
z++
}
sort.Strings(patternPropsKeys)
for k := range patternPropsKeys {
d = append(d, low.GenerateHashString(s.FindPatternProperty(patternPropsKeys[k]).Value))
} }
if len(s.PrefixItems.Value) > 0 { if len(s.PrefixItems.Value) > 0 {
itemsKeys := make([]string, len(s.PrefixItems.Value)) itemsKeys := make([]string, len(s.PrefixItems.Value))
itemsEntities := make(map[string]*SchemaProxy) itemsEntities := make(map[string]*SchemaProxy)
z = 0 z := 0
for i := range s.PrefixItems.Value { for i := range s.PrefixItems.Value {
g := s.PrefixItems.Value[i].Value g := s.PrefixItems.Value[i].Value
r := low.GenerateHashString(g) r := low.GenerateHashString(g)
@@ -411,15 +387,7 @@ func (s *Schema) Hash() [32]byte {
} }
} }
// add extensions to hash d = append(d, low.HashExtensions(s.Extensions)...)
keys = make([]string, len(s.Extensions))
z = 0
for k := range s.Extensions {
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 { if s.Example.Value != nil {
d = append(d, low.GenerateHashString(s.Example.Value)) d = append(d, low.GenerateHashString(s.Example.Value))
} }
@@ -435,12 +403,9 @@ func (s *Schema) Hash() [32]byte {
d = append(d, fmt.Sprint(s.MaxContains.Value)) d = append(d, fmt.Sprint(s.MaxContains.Value))
} }
if !s.Examples.IsEmpty() { if !s.Examples.IsEmpty() {
var xph []string for _, ex := range s.Examples.Value {
for w := range s.Examples.Value { d = append(d, low.GenerateHashString(ex.Value))
xph = append(xph, low.GenerateHashString(s.Examples.Value[w].Value))
} }
sort.Strings(xph)
d = append(d, strings.Join(xph, "|"))
} }
return sha256.Sum256([]byte(strings.Join(d, "|"))) return sha256.Sum256([]byte(strings.Join(d, "|")))
} }
@@ -464,7 +429,7 @@ func (s *Schema) FindPatternProperty(name string) *low.ValueReference[*SchemaPro
} }
// GetExtensions returns all extensions for Schema // GetExtensions returns all extensions for Schema
func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (s *Schema) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return s.Extensions return s.Extensions
} }
@@ -649,20 +614,20 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
} }
// handle example if set. (3.0) // handle example if set. (3.0)
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) _, expLabel, expNode := utils.FindKeyNodeFullTop(ExampleLabel, root.Content)
if expNode != nil { if expNode != nil {
s.Example = low.NodeReference[any]{Value: ExtractExampleValue(expNode), KeyNode: expLabel, ValueNode: expNode} s.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode}
} }
// handle examples if set.(3.1) // handle examples if set.(3.1)
_, expArrLabel, expArrNode := utils.FindKeyNodeFullTop(ExamplesLabel, root.Content) _, expArrLabel, expArrNode := utils.FindKeyNodeFullTop(ExamplesLabel, root.Content)
if expArrNode != nil { if expArrNode != nil {
if utils.IsNodeArray(expArrNode) { if utils.IsNodeArray(expArrNode) {
var examples []low.ValueReference[any] var examples []low.ValueReference[*yaml.Node]
for i := range expArrNode.Content { for i := range expArrNode.Content {
examples = append(examples, low.ValueReference[any]{Value: ExtractExampleValue(expArrNode.Content[i]), ValueNode: expArrNode.Content[i]}) examples = append(examples, low.ValueReference[*yaml.Node]{Value: expArrNode.Content[i], ValueNode: expArrNode.Content[i]})
} }
s.Examples = low.NodeReference[[]low.ValueReference[any]]{ s.Examples = low.NodeReference[[]low.ValueReference[*yaml.Node]]{
Value: examples, Value: examples,
ValueNode: expArrNode, ValueNode: expArrNode,
KeyNode: expArrLabel, KeyNode: expArrLabel,
@@ -1035,7 +1000,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
return nil return nil
} }
func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) { func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) {
_, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content) _, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content)
if propsNode != nil { if propsNode != nil {
propertyMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*SchemaProxy]]() propertyMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*SchemaProxy]]()
@@ -1047,12 +1012,12 @@ func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex
} }
// check our prop isn't reference // check our prop isn't reference
isRef := false
refString := "" refString := ""
var refNode *yaml.Node
if h, _, l := utils.IsNodeRefValue(prop); h { if h, _, l := utils.IsNodeRefValue(prop); h {
ref, _, _, _ := low.LocateRefNodeWithContext(ctx, prop, idx) ref, _, _, _ := low.LocateRefNodeWithContext(ctx, prop, idx)
if ref != nil { if ref != nil {
isRef = true refNode = prop
prop = ref prop = ref
refString = l refString = l
} else { } else {
@@ -1061,16 +1026,19 @@ func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex
} }
} }
sp := &SchemaProxy{ctx: ctx, kn: currentProp, vn: prop, idx: idx}
sp.SetReference(refString, refNode)
propertyMap.Set(low.KeyReference[string]{ propertyMap.Set(low.KeyReference[string]{
KeyNode: currentProp, KeyNode: currentProp,
Value: currentProp.Value, Value: currentProp.Value,
}, low.ValueReference[*SchemaProxy]{ }, low.ValueReference[*SchemaProxy]{
Value: &SchemaProxy{ctx: ctx, kn: currentProp, vn: prop, idx: idx, isReference: isRef, referenceLookup: refString}, Value: sp,
ValueNode: prop, ValueNode: prop,
}) })
} }
return &low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{ return &low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{
Value: propertyMap, Value: propertyMap,
KeyNode: propLabel, KeyNode: propLabel,
ValueNode: propsNode, ValueNode: propsNode,
@@ -1112,7 +1080,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
syncChan := make(chan buildResult) syncChan := make(chan buildResult)
// build out a SchemaProxy for every sub-schema. // build out a SchemaProxy for every sub-schema.
build := func(pctx context.Context, kn *yaml.Node, vn *yaml.Node, schemaIdx int, c chan buildResult, build := func(pctx context.Context, kn, vn *yaml.Node, rf *yaml.Node, schemaIdx int, c chan buildResult,
isRef bool, refLocation string, isRef bool, refLocation string,
) { ) {
// a proxy design works best here. polymorphism, pretty much guarantees that a sub-schema can // a proxy design works best here. polymorphism, pretty much guarantees that a sub-schema can
@@ -1127,8 +1095,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
sp.idx = idx sp.idx = idx
sp.ctx = pctx sp.ctx = pctx
if isRef { if isRef {
sp.referenceLookup = refLocation sp.SetReference(refLocation, rf)
sp.isReference = true
} }
res := &low.ValueReference[*SchemaProxy]{ res := &low.ValueReference[*SchemaProxy]{
Value: sp, Value: sp,
@@ -1142,6 +1109,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
isRef := false isRef := false
refLocation := "" refLocation := ""
var refNode *yaml.Node
foundCtx := ctx foundCtx := ctx
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
h := false h := false
@@ -1149,6 +1117,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
isRef = true isRef = true
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, valueNode, idx) ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, valueNode, idx)
if ref != nil { if ref != nil {
refNode = valueNode
valueNode = ref valueNode = ref
foundCtx = fctx foundCtx = fctx
} else { } else {
@@ -1159,7 +1128,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
// this only runs once, however to keep things consistent, it makes sense to use the same async method // this only runs once, however to keep things consistent, it makes sense to use the same async method
// that arrays will use. // that arrays will use.
go build(foundCtx, labelNode, valueNode, -1, syncChan, isRef, refLocation) go build(foundCtx, labelNode, valueNode, refNode, -1, syncChan, isRef, refLocation)
select { select {
case r := <-syncChan: case r := <-syncChan:
schemas <- schemaProxyBuildResult{ schemas <- schemaProxyBuildResult{
@@ -1181,6 +1150,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
isRef = true isRef = true
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, vn, idx) ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, vn, idx)
if ref != nil { if ref != nil {
refNode = vn
vn = ref vn = ref
foundCtx = fctx foundCtx = fctx
} else { } else {
@@ -1191,7 +1161,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
} }
} }
refBuilds++ refBuilds++
go build(foundCtx, vn, vn, i, syncChan, isRef, refLocation) go build(foundCtx, vn, vn, refNode, i, syncChan, isRef, refLocation)
} }
completedBuilds := 0 completedBuilds := 0
@@ -1225,12 +1195,11 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
var schLabel, schNode *yaml.Node var schLabel, schNode *yaml.Node
errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d" errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d"
isRef := false
refLocation := "" refLocation := ""
var refNode *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf { if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index. // locate reference in index.
isRef = true
ref, fIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, root, idx) ref, fIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, root, idx)
if ref != nil { if ref != nil {
schNode = ref schNode = ref
@@ -1250,9 +1219,9 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
if schNode != nil { if schNode != nil {
h := false h := false
if h, _, refLocation = utils.IsNodeRefValue(schNode); h { if h, _, refLocation = utils.IsNodeRefValue(schNode); h {
isRef = true
ref, foundIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, schNode, idx) ref, foundIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, schNode, idx)
if ref != nil { if ref != nil {
refNode = schNode
schNode = ref schNode = ref
if foundIdx != nil { if foundIdx != nil {
// TODO: check on this // TODO: check on this
@@ -1273,11 +1242,16 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
if schNode != nil { if schNode != nil {
// check if schema has already been built. // check if schema has already been built.
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, ctx: ctx, isReference: isRef, referenceLookup: refLocation} schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, ctx: ctx}
return &low.NodeReference[*SchemaProxy]{ schema.SetReference(refLocation, refNode)
Value: schema, KeyNode: schLabel, ValueNode: schNode, ReferenceNode: isRef,
Reference: refLocation, n := &low.NodeReference[*SchemaProxy]{
}, nil Value: schema,
KeyNode: schLabel,
ValueNode: schNode,
}
n.SetReference(refLocation, refNode)
return n, nil
} }
return nil, nil return nil, nil
} }

View File

@@ -6,6 +6,8 @@ package base
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -44,13 +46,12 @@ import (
// it's not actually JSONSchema until 3.1, so lots of times a bad schema will break parsing. Errors are only found // it's not actually JSONSchema until 3.1, so lots of times a bad schema will break parsing. Errors are only found
// when a schema is needed, so the rest of the document is parsed and ready to use. // when a schema is needed, so the rest of the document is parsed and ready to use.
type SchemaProxy struct { type SchemaProxy struct {
low.Reference
kn *yaml.Node kn *yaml.Node
vn *yaml.Node vn *yaml.Node
idx *index.SpecIndex idx *index.SpecIndex
rendered *Schema rendered *Schema
buildError error buildError error
isReference bool // Is the schema underneath originally a $ref?
referenceLookup string // If the schema is a $ref, what's its name?
ctx context.Context ctx context.Context
} }
@@ -62,8 +63,7 @@ func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *in
sp.idx = idx sp.idx = idx
sp.ctx = ctx sp.ctx = ctx
if rf, _, r := utils.IsNodeRefValue(value); rf { if rf, _, r := utils.IsNodeRefValue(value); rf {
sp.isReference = true sp.SetReference(r, value)
sp.referenceLookup = r
} }
return nil return nil
} }
@@ -101,36 +101,6 @@ func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError return sp.buildError
} }
// IsSchemaReference returns true if the Schema that this SchemaProxy represents, is actually a reference to
// a Schema contained within Components or Definitions. There is no difference in the mechanism used to resolve the
// Schema when calling Schema(), however if we want to know if this schema was originally a reference, we won't
// be able to determine that from the model, without this bit.
func (sp *SchemaProxy) IsSchemaReference() bool {
return sp.isReference
}
// IsReference is an alias for IsSchemaReference() except it's compatible wih the IsReferenced interface type.
func (sp *SchemaProxy) IsReference() bool {
return sp.IsSchemaReference()
}
// GetReference is an alias for GetSchemaReference() except it's compatible wih the IsReferenced interface type.
func (sp *SchemaProxy) GetReference() string {
return sp.GetSchemaReference()
}
// SetReference will set the reference lookup for this SchemaProxy.
func (sp *SchemaProxy) SetReference(ref string) {
sp.referenceLookup = ref
}
// GetSchemaReference will return the lookup defined by the $ref that this schema points to. If the schema
// is inline, and not a reference, then this method returns an empty string. Only useful when combined with
// IsSchemaReference()
func (sp *SchemaProxy) GetSchemaReference() string {
return sp.referenceLookup
}
func (sp *SchemaProxy) GetSchemaReferenceLocation() *index.NodeOrigin { func (sp *SchemaProxy) GetSchemaReferenceLocation() *index.NodeOrigin {
if sp.idx != nil { if sp.idx != nil {
origin := sp.idx.FindNodeOrigin(sp.vn) origin := sp.idx.FindNodeOrigin(sp.vn)
@@ -158,11 +128,11 @@ func (sp *SchemaProxy) GetValueNode() *yaml.Node {
// Hash will return a consistent SHA256 Hash of the SchemaProxy object (it will resolve it) // Hash will return a consistent SHA256 Hash of the SchemaProxy object (it will resolve it)
func (sp *SchemaProxy) Hash() [32]byte { func (sp *SchemaProxy) Hash() [32]byte {
if sp.rendered != nil { if sp.rendered != nil {
if !sp.isReference { if !sp.IsReference() {
return sp.rendered.Hash() return sp.rendered.Hash()
} }
} else { } else {
if !sp.isReference { if !sp.IsReference() {
// only resolve this proxy if it's not a ref. // only resolve this proxy if it's not a ref.
sch := sp.Schema() sch := sp.Schema()
sp.rendered = sch sp.rendered = sch
@@ -170,5 +140,5 @@ func (sp *SchemaProxy) Hash() [32]byte {
} }
} }
// hash reference value only, do not resolve! // hash reference value only, do not resolve!
return sha256.Sum256([]byte(sp.referenceLookup)) return sha256.Sum256([]byte(sp.GetReference()))
} }

View File

@@ -5,15 +5,16 @@ package base
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestSchemaProxy_Build(t *testing.T) { func TestSchemaProxy_Build(t *testing.T) {
yml := `x-windows: washed yml := `x-windows: washed
description: something` description: something`
@@ -24,29 +25,25 @@ description: something`
err := sch.Build(context.Background(), &idxNode, idxNode.Content[0], nil) err := sch.Build(context.Background(), &idxNode, idxNode.Content[0], nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23", assert.Equal(t, "e20c009d370944d177c0b46e8fa29e15fadc3a6f9cca6bb251ff9e120265fc96",
low.GenerateHashString(&sch)) low.GenerateHashString(&sch))
assert.Equal(t, "something", sch.Schema().Description.Value) assert.Equal(t, "something", sch.Schema().Description.GetValue())
assert.Empty(t, sch.GetSchemaReference()) assert.Empty(t, sch.GetReference())
assert.NotNil(t, sch.GetKeyNode()) assert.NotNil(t, sch.GetKeyNode())
assert.NotNil(t, sch.GetValueNode()) assert.NotNil(t, sch.GetValueNode())
assert.False(t, sch.IsSchemaReference())
assert.False(t, sch.IsReference()) assert.False(t, sch.IsReference())
assert.Empty(t, sch.GetReference()) sch.SetReference("coffee", nil)
sch.SetReference("coffee")
assert.Equal(t, "coffee", sch.GetReference()) assert.Equal(t, "coffee", sch.GetReference())
// already rendered, should spit out the same // already rendered, should spit out the same
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23", assert.Equal(t, "37290d74ac4d186e3a8e5785d259d2ec04fac91ae28092e7620ec8bc99e830aa",
low.GenerateHashString(&sch)) low.GenerateHashString(&sch))
assert.Len(t, sch.Schema().GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(sch.Schema().GetExtensions()))
} }
func TestSchemaProxy_Build_CheckRef(t *testing.T) { func TestSchemaProxy_Build_CheckRef(t *testing.T) {
yml := `$ref: wat` yml := `$ref: wat`
var sch SchemaProxy var sch SchemaProxy
@@ -55,14 +52,13 @@ func TestSchemaProxy_Build_CheckRef(t *testing.T) {
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil) err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, sch.IsSchemaReference()) assert.True(t, sch.IsReference())
assert.Equal(t, "wat", sch.GetSchemaReference()) assert.Equal(t, "wat", sch.GetReference())
assert.Equal(t, "f00a787f7492a95e165b470702f4fe9373583fbdc025b2c8bdf0262cc48fcff4", assert.Equal(t, "f00a787f7492a95e165b470702f4fe9373583fbdc025b2c8bdf0262cc48fcff4",
low.GenerateHashString(&sch)) low.GenerateHashString(&sch))
} }
func TestSchemaProxy_Build_HashInline(t *testing.T) { func TestSchemaProxy_Build_HashInline(t *testing.T) {
yml := `type: int` yml := `type: int`
var sch SchemaProxy var sch SchemaProxy
@@ -71,14 +67,13 @@ func TestSchemaProxy_Build_HashInline(t *testing.T) {
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil) err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, sch.IsSchemaReference()) assert.False(t, sch.IsReference())
assert.NotNil(t, sch.Schema()) assert.NotNil(t, sch.Schema())
assert.Equal(t, "6da88c34ba124c41f977db66a4fc5c1a951708d285c81bb0d47c3206f4c27ca8", assert.Equal(t, "6da88c34ba124c41f977db66a4fc5c1a951708d285c81bb0d47c3206f4c27ca8",
low.GenerateHashString(&sch)) low.GenerateHashString(&sch))
} }
func TestSchemaProxy_Build_UsingMergeNodes(t *testing.T) { func TestSchemaProxy_Build_UsingMergeNodes(t *testing.T) {
yml := ` yml := `
x-common-definitions: x-common-definitions:
life_cycle_types: &life_cycle_types_def life_cycle_types: &life_cycle_types_def
@@ -95,11 +90,9 @@ x-common-definitions:
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, sch.Schema().Enum.Value, 3) assert.Len(t, sch.Schema().Enum.Value, 3)
assert.Equal(t, "The type of life cycle", sch.Schema().Description.Value) assert.Equal(t, "The type of life cycle", sch.Schema().Description.Value)
} }
func TestSchemaProxy_GetSchemaReferenceLocation(t *testing.T) { func TestSchemaProxy_GetSchemaReferenceLocation(t *testing.T) {
yml := `type: object yml := `type: object
properties: properties:
name: name:
@@ -159,5 +152,4 @@ properties:
err = schC.Build(context.Background(), nil, idxNodeA.Content[0], nil) err = schC.Build(context.Background(), nil, idxNodeA.Content[0], nil)
origin = schC.GetSchemaReferenceLocation() origin = schC.GetSchemaReferenceLocation()
assert.Nil(t, origin) assert.Nil(t, origin)
} }

View File

@@ -2,6 +2,8 @@ package base
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -9,7 +11,6 @@ import (
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func test_get_schema_blob() string { func test_get_schema_blob() string {
@@ -192,8 +193,8 @@ func Test_Schema(t *testing.T) {
assert.Equal(t, "an xml namespace", j.XML.Value.Namespace.Value) assert.Equal(t, "an xml namespace", j.XML.Value.Namespace.Value)
assert.Equal(t, "a prefix", j.XML.Value.Prefix.Value) assert.Equal(t, "a prefix", j.XML.Value.Prefix.Value)
assert.Equal(t, true, j.XML.Value.Attribute.Value) assert.Equal(t, true, j.XML.Value.Attribute.Value)
assert.Len(t, j.XML.Value.Extensions, 1) assert.Equal(t, 1, orderedmap.Len(j.XML.Value.Extensions))
assert.Len(t, j.XML.Value.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(j.XML.Value.GetExtensions()))
assert.NotNil(t, v.Value.Schema().AdditionalProperties.Value) assert.NotNil(t, v.Value.Schema().AdditionalProperties.Value)
@@ -213,12 +214,20 @@ func Test_Schema(t *testing.T) {
io := v.Value.Schema() io := v.Value.Schema()
assert.Equal(t, "allOfA description", io.Description.Value) assert.Equal(t, "allOfA description", io.Description.Value)
assert.Equal(t, "allOfAExp", io.Example.Value)
var ioExample string
_ = io.Example.GetValueNode().Decode(&ioExample)
assert.Equal(t, "allOfAExp", ioExample)
qw := f.FindProperty("allOfB").Value.Schema() qw := f.FindProperty("allOfB").Value.Schema()
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "allOfB description", qw.Description.Value) assert.Equal(t, "allOfB description", qw.Description.Value)
assert.Equal(t, "allOfBExp", qw.Example.Value)
var qwExample string
_ = qw.Example.GetValueNode().Decode(&qwExample)
assert.Equal(t, "allOfBExp", qwExample)
// check polymorphic values anyOf // check polymorphic values anyOf
assert.Equal(t, "an anyOf thing", sch.AnyOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, "an anyOf thing", sch.AnyOf.Value[0].Value.Schema().Description.Value)
@@ -227,12 +236,18 @@ func Test_Schema(t *testing.T) {
v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfA") v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "anyOfA description", v.Value.Schema().Description.Value) assert.Equal(t, "anyOfA description", v.Value.Schema().Description.Value)
assert.Equal(t, "anyOfAExp", v.Value.Schema().Example.Value)
var vSchemaExample string
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "anyOfAExp", vSchemaExample)
v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfB") v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "anyOfB description", v.Value.Schema().Description.Value) assert.Equal(t, "anyOfB description", v.Value.Schema().Description.Value)
assert.Equal(t, "anyOfBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "anyOfBExp", vSchemaExample)
// check polymorphic values oneOf // check polymorphic values oneOf
assert.Equal(t, "a oneof thing", sch.OneOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, "a oneof thing", sch.OneOf.Value[0].Value.Schema().Description.Value)
@@ -241,12 +256,16 @@ func Test_Schema(t *testing.T) {
v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfA") v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "oneOfA description", v.Value.Schema().Description.Value) assert.Equal(t, "oneOfA description", v.Value.Schema().Description.Value)
assert.Equal(t, "oneOfAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "oneOfAExp", vSchemaExample)
v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfB") v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "oneOfB description", v.Value.Schema().Description.Value) assert.Equal(t, "oneOfB description", v.Value.Schema().Description.Value)
assert.Equal(t, "oneOfBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "oneOfBExp", vSchemaExample)
// check values NOT // check values NOT
assert.Equal(t, "a not thing", sch.Not.Value.Schema().Description.Value) assert.Equal(t, "a not thing", sch.Not.Value.Schema().Description.Value)
@@ -255,12 +274,16 @@ func Test_Schema(t *testing.T) {
v = sch.Not.Value.Schema().FindProperty("notA") v = sch.Not.Value.Schema().FindProperty("notA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "notA description", v.Value.Schema().Description.Value) assert.Equal(t, "notA description", v.Value.Schema().Description.Value)
assert.Equal(t, "notAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "notAExp", vSchemaExample)
v = sch.Not.Value.Schema().FindProperty("notB") v = sch.Not.Value.Schema().FindProperty("notB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "notB description", v.Value.Schema().Description.Value) assert.Equal(t, "notB description", v.Value.Schema().Description.Value)
assert.Equal(t, "notBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "notBExp", vSchemaExample)
// check values Items // check values Items
assert.Equal(t, "an items thing", sch.Items.Value.A.Schema().Description.Value) assert.Equal(t, "an items thing", sch.Items.Value.A.Schema().Description.Value)
@@ -269,12 +292,16 @@ func Test_Schema(t *testing.T) {
v = sch.Items.Value.A.Schema().FindProperty("itemsA") v = sch.Items.Value.A.Schema().FindProperty("itemsA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value) assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "itemsAExp", vSchemaExample)
v = sch.Items.Value.A.Schema().FindProperty("itemsB") v = sch.Items.Value.A.Schema().FindProperty("itemsB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value) assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "itemsBExp", vSchemaExample)
// check values PrefixItems // check values PrefixItems
assert.Equal(t, "an items thing", sch.PrefixItems.Value[0].Value.Schema().Description.Value) assert.Equal(t, "an items thing", sch.PrefixItems.Value[0].Value.Schema().Description.Value)
@@ -283,17 +310,21 @@ func Test_Schema(t *testing.T) {
v = sch.PrefixItems.Value[0].Value.Schema().FindProperty("itemsA") v = sch.PrefixItems.Value[0].Value.Schema().FindProperty("itemsA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value) assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "itemsAExp", vSchemaExample)
v = sch.PrefixItems.Value[0].Value.Schema().FindProperty("itemsB") v = sch.PrefixItems.Value[0].Value.Schema().FindProperty("itemsB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value) assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValue().Decode(&vSchemaExample)
assert.Equal(t, "itemsBExp", vSchemaExample)
// check discriminator // check discriminator
assert.NotNil(t, sch.Discriminator.Value) assert.NotNil(t, sch.Discriminator.Value)
assert.Equal(t, "athing", sch.Discriminator.Value.PropertyName.Value) assert.Equal(t, "athing", sch.Discriminator.Value.PropertyName.Value)
assert.Len(t, sch.Discriminator.Value.Mapping.Value, 2) assert.Equal(t, 2, sch.Discriminator.GetValue().Mapping.GetValue().Len())
mv := sch.Discriminator.Value.FindMappingValue("log") mv := sch.Discriminator.Value.FindMappingValue("log")
assert.Equal(t, "cat", mv.Value) assert.Equal(t, "cat", mv.Value)
mv = sch.Discriminator.Value.FindMappingValue("pizza") mv = sch.Discriminator.Value.FindMappingValue("pizza")
@@ -429,12 +460,20 @@ const: tasty`
assert.Equal(t, float64(12), sch.ExclusiveMinimum.Value.B) assert.Equal(t, float64(12), sch.ExclusiveMinimum.Value.B)
assert.Equal(t, float64(13), sch.ExclusiveMaximum.Value.B) assert.Equal(t, float64(13), sch.ExclusiveMaximum.Value.B)
assert.Len(t, sch.Examples.Value, 1) assert.Len(t, sch.Examples.Value, 1)
assert.Equal(t, "testing", sch.Examples.Value[0].Value)
var example0 string
_ = sch.Examples.GetValue()[0].GetValue().Decode(&example0)
assert.Equal(t, "testing", example0)
assert.Equal(t, "fish64", sch.ContentEncoding.Value) assert.Equal(t, "fish64", sch.ContentEncoding.Value)
assert.Equal(t, "fish/paste", sch.ContentMediaType.Value) assert.Equal(t, "fish/paste", sch.ContentMediaType.Value)
assert.True(t, sch.Items.Value.IsB()) assert.True(t, sch.Items.Value.IsB())
assert.True(t, sch.Items.Value.B) assert.True(t, sch.Items.Value.B)
assert.Equal(t, "tasty", sch.Const.Value)
var schConst string
_ = sch.Const.GetValue().Decode(&schConst)
assert.Equal(t, "tasty", schConst)
} }
func TestSchema_Build_PropsLookup(t *testing.T) { func TestSchema_Build_PropsLookup(t *testing.T) {
@@ -986,7 +1025,11 @@ schema:
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, res.Value) assert.NotNil(t, res.Value)
sch := res.Value.Schema() sch := res.Value.Schema()
assert.Equal(t, 5, sch.Default.Value)
var def int
_ = sch.Default.GetValueNode().Decode(&def)
assert.Equal(t, 5, def)
} }
func TestExtractSchema_ConstPrimitive(t *testing.T) { func TestExtractSchema_ConstPrimitive(t *testing.T) {
@@ -1002,7 +1045,11 @@ schema:
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, res.Value) assert.NotNil(t, res.Value)
sch := res.Value.Schema() sch := res.Value.Schema()
assert.Equal(t, 5, sch.Const.Value)
var cnst int
_ = sch.Const.GetValueNode().Decode(&cnst)
assert.Equal(t, 5, cnst)
} }
func TestExtractSchema_Ref(t *testing.T) { func TestExtractSchema_Ref(t *testing.T) {
@@ -1785,7 +1832,6 @@ components:
res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx) res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res) assert.Nil(t, res)
assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 2, col 9", e.Error()) assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 2, col 9", e.Error())
} }
func TestSchema_EmptyRef(t *testing.T) { func TestSchema_EmptyRef(t *testing.T) {
@@ -1814,5 +1860,4 @@ components:
res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx) res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res) assert.Nil(t, res)
assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 1, col 7", e.Error()) assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 1, col 7", e.Error())
} }

View File

@@ -26,7 +26,7 @@ import (
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
// - https://swagger.io/specification/#security-requirement-object // - https://swagger.io/specification/#security-requirement-object
type SecurityRequirement struct { type SecurityRequirement struct {
Requirements low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]] Requirements low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]
*low.Reference *low.Reference
} }
@@ -61,7 +61,7 @@ func (s *SecurityRequirement) Build(_ context.Context, _, root *yaml.Node, _ *in
}, },
) )
} }
s.Requirements = low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]{ s.Requirements = low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]{
Value: valueMap, Value: valueMap,
ValueNode: root, ValueNode: root,
} }
@@ -91,22 +91,14 @@ func (s *SecurityRequirement) GetKeys() []string {
// Hash will return a consistent SHA256 Hash of the SecurityRequirement object // Hash will return a consistent SHA256 Hash of the SecurityRequirement object
func (s *SecurityRequirement) Hash() [32]byte { func (s *SecurityRequirement) Hash() [32]byte {
var f []string var f []string
values := make(map[string][]string, orderedmap.Len(s.Requirements.Value)) for pair := orderedmap.First(orderedmap.SortAlpha(s.Requirements.Value)); pair != nil; pair = pair.Next() {
var valKeys []string
for pair := orderedmap.First(s.Requirements.Value); pair != nil; pair = pair.Next() {
var vals []string var vals []string
for y := range pair.Value().Value { for y := range pair.Value().Value {
vals = append(vals, pair.Value().Value[y].Value) vals = append(vals, pair.Value().Value[y].Value)
} }
sort.Strings(vals) sort.Strings(vals)
valKeys = append(valKeys, pair.Key().Value)
if len(vals) > 0 { f = append(f, fmt.Sprintf("%s-%s", pair.Key().Value, strings.Join(vals, "|")))
values[pair.Key().Value] = vals
}
}
sort.Strings(valKeys)
for val := range valKeys {
f = append(f, fmt.Sprintf("%s-%s", valKeys[val], strings.Join(values[valKeys[val]], "|")))
} }
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -6,13 +6,13 @@ package base
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort"
"strings"
) )
// Tag represents a low-level Tag instance that is backed by a low-level one. // Tag represents a low-level Tag instance that is backed by a low-level one.
@@ -25,13 +25,13 @@ type Tag struct {
Name low.NodeReference[string] Name low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
ExternalDocs low.NodeReference[*ExternalDoc] ExternalDocs low.NodeReference[*ExternalDoc]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference *low.Reference
} }
// FindExtension returns a ValueReference containing the extension value, if found. // FindExtension returns a ValueReference containing the extension value, if found.
func (t *Tag) FindExtension(ext string) *low.ValueReference[any] { func (t *Tag) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, t.Extensions) return low.FindItemInOrderedMap(ext, t.Extensions)
} }
// Build will extract extensions and external docs for the Tag. // Build will extract extensions and external docs for the Tag.
@@ -48,7 +48,7 @@ func (t *Tag) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecInde
} }
// GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface.
func (t *Tag) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (t *Tag) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return t.Extensions return t.Extensions
} }
@@ -64,13 +64,6 @@ func (t *Tag) Hash() [32]byte {
if !t.ExternalDocs.IsEmpty() { if !t.ExternalDocs.IsEmpty() {
f = append(f, low.GenerateHashString(t.ExternalDocs.Value)) f = append(f, low.GenerateHashString(t.ExternalDocs.Value))
} }
keys := make([]string, len(t.Extensions)) f = append(f, low.HashExtensions(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, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -5,15 +5,16 @@ package base
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestTag_Build(t *testing.T) { func TestTag_Build(t *testing.T) {
yml := `name: a tag yml := `name: a tag
description: a description description: a description
externalDocs: externalDocs:
@@ -33,13 +34,15 @@ x-coffee: tasty`
assert.Equal(t, "a tag", n.Name.Value) assert.Equal(t, "a tag", n.Name.Value)
assert.Equal(t, "a description", n.Description.Value) assert.Equal(t, "a description", n.Description.Value)
assert.Equal(t, "https://pb33f.io", n.ExternalDocs.Value.URL.Value) assert.Equal(t, "https://pb33f.io", n.ExternalDocs.Value.URL.Value)
assert.Equal(t, "tasty", n.FindExtension("x-coffee").Value)
assert.Len(t, n.GetExtensions(), 1)
var xCoffee string
_ = n.FindExtension("x-coffee").GetValue().Decode(&xCoffee)
assert.Equal(t, "tasty", xCoffee)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }
func TestTag_Build_Error(t *testing.T) { func TestTag_Build_Error(t *testing.T) {
yml := `name: a tag yml := `name: a tag
description: a description description: a description
externalDocs: externalDocs:
@@ -58,7 +61,6 @@ externalDocs:
} }
func TestTag_Hash(t *testing.T) { func TestTag_Hash(t *testing.T) {
left := `name: melody left := `name: melody
description: my princess description: my princess
externalDocs: externalDocs:
@@ -84,5 +86,4 @@ x-b33f: princess`
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil) _ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash()) assert.Equal(t, lDoc.Hash(), rDoc.Hash())
} }

View File

@@ -3,12 +3,13 @@ package base
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort"
"strings"
) )
// XML represents a low-level representation of an XML object defined by all versions of OpenAPI. // XML represents a low-level representation of an XML object defined by all versions of OpenAPI.
@@ -26,7 +27,7 @@ type XML struct {
Prefix low.NodeReference[string] Prefix low.NodeReference[string]
Attribute low.NodeReference[bool] Attribute low.NodeReference[bool]
Wrapped low.NodeReference[bool] Wrapped low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference *low.Reference
} }
@@ -40,7 +41,7 @@ func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
} }
// GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface.
func (x *XML) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (x *XML) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return x.Extensions return x.Extensions
} }
@@ -62,13 +63,6 @@ func (x *XML) Hash() [32]byte {
if !x.Wrapped.IsEmpty() { if !x.Wrapped.IsEmpty() {
f = append(f, fmt.Sprint(x.Wrapped.Value)) f = append(f, fmt.Sprint(x.Wrapped.Value))
} }
keys := make([]string, len(x.Extensions)) f = append(f, low.HashExtensions(x.Extensions)...)
z := 0
for k := range x.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(x.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -7,37 +7,24 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"net/url"
"path/filepath"
"reflect" "reflect"
"strconv"
"strings" "strings"
"sync"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"net/url"
"path/filepath"
) )
// FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every
// KeyReference will have its value checked against the string key and if there is a match, it will be returned.
func FindItemInMap[T any](item string, collection map[KeyReference[string]]ValueReference[T]) *ValueReference[T] {
for n, o := range collection {
if n.Value == item {
return &o
}
if strings.EqualFold(item, n.Value) {
return &o
}
}
return nil
}
// FindItemInOrderedMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. // FindItemInOrderedMap accepts a string key and a collection of KeyReference[string] and ValueReference[T].
// Every KeyReference will have its value checked against the string key and if there is a match, it will be // Every KeyReference will have its value checked against the string key and if there is a match, it will be
// returned. // returned.
func FindItemInOrderedMap[T any](item string, collection orderedmap.Map[KeyReference[string], ValueReference[T]]) *ValueReference[T] { func FindItemInOrderedMap[T any](item string, collection *orderedmap.Map[KeyReference[string], ValueReference[T]]) *ValueReference[T] {
for pair := orderedmap.First(collection); pair != nil; pair = pair.Next() { for pair := orderedmap.First(collection); pair != nil; pair = pair.Next() {
n := pair.Key() n := pair.Key()
if n.Value == item { if n.Value == item {
@@ -50,6 +37,18 @@ func FindItemInOrderedMap[T any](item string, collection orderedmap.Map[KeyRefer
return nil return nil
} }
// HashExtensions will generate a hash from the low representation of extensions.
func HashExtensions(ext *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]) []string {
f := []string{}
for pair := orderedmap.First(orderedmap.SortAlpha(ext)); pair != nil; pair = pair.Next() {
b, _ := yaml.Marshal(pair.Value().GetValue())
f = append(f, fmt.Sprintf("%s-%x", pair.Key().Value, sha256.Sum256([]byte(b))))
}
return f
}
// helper function to generate a list of all the things an index should be searched for. // helper function to generate a list of all the things an index should be searched for.
func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference { func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference {
return []func() map[string]*index.Reference{ return []func() map[string]*index.Reference{
@@ -68,7 +67,6 @@ func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Re
} }
func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, *index.SpecIndex, error, context.Context) { func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, *index.SpecIndex, error, context.Context) {
if rf, _, rv := utils.IsNodeRefValue(root); rf { if rf, _, rv := utils.IsNodeRefValue(root); rf {
if rv == "" { if rv == "" {
@@ -112,9 +110,7 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
explodedRefValue := strings.Split(rv, "#") explodedRefValue := strings.Split(rv, "#")
if len(explodedRefValue) == 2 { if len(explodedRefValue) == 2 {
if !strings.HasPrefix(explodedRefValue[0], "http") { if !strings.HasPrefix(explodedRefValue[0], "http") {
if !filepath.IsAbs(explodedRefValue[0]) { if !filepath.IsAbs(explodedRefValue[0]) {
if strings.HasPrefix(specPath, "http") { if strings.HasPrefix(specPath, "http") {
u, _ := url.Parse(specPath) u, _ := url.Parse(specPath)
p := "" p := ""
@@ -137,7 +133,6 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
} }
rv = fmt.Sprintf("%s#%s", abs, explodedRefValue[1]) rv = fmt.Sprintf("%s#%s", abs, explodedRefValue[1])
} else { } else {
// check for a config baseURL and use that if it exists. // check for a config baseURL and use that if it exists.
if idx.GetConfig().BaseURL != nil { if idx.GetConfig().BaseURL != nil {
@@ -154,11 +149,8 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
} }
} }
} else { } else {
if !strings.HasPrefix(explodedRefValue[0], "http") { if !strings.HasPrefix(explodedRefValue[0], "http") {
if !filepath.IsAbs(explodedRefValue[0]) { if !filepath.IsAbs(explodedRefValue[0]) {
if strings.HasPrefix(specPath, "http") { if strings.HasPrefix(specPath, "http") {
u, _ := url.Parse(specPath) u, _ := url.Parse(specPath)
p := filepath.Dir(u.Path) p := filepath.Dir(u.Path)
@@ -173,7 +165,6 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
rv = abs rv = abs
} else { } else {
// check for a config baseURL and use that if it exists. // check for a config baseURL and use that if it exists.
if idx.GetConfig().BaseURL != nil { if idx.GetConfig().BaseURL != nil {
u := *idx.GetConfig().BaseURL u := *idx.GetConfig().BaseURL
@@ -211,7 +202,6 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
rv, root.Line, root.Column), ctx rv, root.Line, root.Column), ctx
} }
return nil, idx, nil, ctx return nil, idx, nil, ctx
} }
// LocateRefNode will perform a complete lookup for a $ref node. This function searches the entire index for // LocateRefNode will perform a complete lookup for a $ref node. This function searches the entire index for
@@ -227,10 +217,12 @@ func ExtractObjectRaw[T Buildable[N], N any](ctx context.Context, key, root *yam
var circError error var circError error
var isReference bool var isReference bool
var referenceValue string var referenceValue string
var refNode *yaml.Node
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
if h, _, rv := utils.IsNodeRefValue(root); h { if h, _, rv := utils.IsNodeRefValue(root); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx) ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil { if ref != nil {
refNode = root
root = ref root = ref
isReference = true isReference = true
referenceValue = rv referenceValue = rv
@@ -257,7 +249,7 @@ func ExtractObjectRaw[T Buildable[N], N any](ctx context.Context, key, root *yam
// if this is a reference, keep track of the reference in the value // if this is a reference, keep track of the reference in the value
if isReference { if isReference {
SetReference(n, referenceValue) SetReference(n, referenceValue, refNode)
} }
// do we want to throw an error as well if circular error reporting is on? // do we want to throw an error as well if circular error reporting is on?
@@ -274,10 +266,12 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
var circError error var circError error
var isReference bool var isReference bool
var referenceValue string var referenceValue string
var refNode *yaml.Node
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
if rf, rl, refVal := utils.IsNodeRefValue(root); rf { if rf, rl, refVal := utils.IsNodeRefValue(root); rf {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx) ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil { if ref != nil {
refNode = root
vn = ref vn = ref
ln = rl ln = rl
isReference = true isReference = true
@@ -298,6 +292,7 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
if h, _, rVal := utils.IsNodeRefValue(vn); h { if h, _, rVal := utils.IsNodeRefValue(vn); h {
ref, fIdx, lerr, nCtx := LocateRefNodeWithContext(ctx, vn, idx) ref, fIdx, lerr, nCtx := LocateRefNodeWithContext(ctx, vn, idx)
if ref != nil { if ref != nil {
refNode = vn
vn = ref vn = ref
if fIdx != nil { if fIdx != nil {
idx = fIdx idx = fIdx
@@ -331,16 +326,15 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
// if this is a reference, keep track of the reference in the value // if this is a reference, keep track of the reference in the value
if isReference { if isReference {
SetReference(n, referenceValue) SetReference(n, referenceValue, refNode)
} }
res := NodeReference[T]{ res := NodeReference[T]{
Value: n, Value: n,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
ReferenceNode: isReference,
Reference: referenceValue,
} }
res.SetReference(referenceValue, refNode)
// do we want to throw an error as well if circular error reporting is on? // do we want to throw an error as well if circular error reporting is on?
if circError != nil && !idx.AllowCircularReferenceResolving() { if circError != nil && !idx.AllowCircularReferenceResolving() {
@@ -349,12 +343,13 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
return res, nil return res, nil
} }
func SetReference(obj any, ref string) { func SetReference(obj any, ref string, refNode *yaml.Node) {
if obj == nil { if obj == nil {
return return
} }
if r, ok := obj.(IsReferenced); ok {
r.SetReference(ref) if r, ok := obj.(SetReferencer); ok {
r.SetReference(ref, refNode)
} }
} }
@@ -429,9 +424,12 @@ func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root
foundCtx := ctx foundCtx := ctx
foundIndex := idx foundIndex := idx
var refNode *yaml.Node
if rf, _, rv := utils.IsNodeRefValue(node); rf { if rf, _, rv := utils.IsNodeRefValue(node); rf {
refg, fIdx, err, nCtx := LocateRefEnd(ctx, node, idx, 0) refg, fIdx, err, nCtx := LocateRefEnd(ctx, node, idx, 0)
if refg != nil { if refg != nil {
refNode = node
node = refg node = refg
localReferenceValue = rv localReferenceValue = rv
foundIndex = fIdx foundIndex = fIdx
@@ -457,15 +455,16 @@ func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root
} }
if localReferenceValue != "" { if localReferenceValue != "" {
SetReference(n, localReferenceValue) SetReference(n, localReferenceValue, refNode)
} }
items = append(items, ValueReference[T]{ v := ValueReference[T]{
Value: n, Value: n,
ValueNode: node, ValueNode: node,
ReferenceNode: localReferenceValue != "", }
Reference: localReferenceValue, v.SetReference(localReferenceValue, refNode)
})
items = append(items, v)
} }
} }
// include circular errors? // include circular errors?
@@ -475,23 +474,6 @@ func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root
return items, ln, vn, nil return items, ln, vn, nil
} }
// ExtractExample will extract a value supplied as an example into a NodeReference. Value can be anything.
// the node value is untyped, so casting will be required when trying to use it.
func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
ref := NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
if utils.IsNodeMap(expNode) {
var decoded map[string]interface{}
_ = expNode.Decode(&decoded)
ref.Value = decoded
}
if utils.IsNodeArray(expNode) {
var decoded []interface{}
_ = expNode.Decode(&decoded)
ref.Value = decoded
}
return ref
}
// ExtractMapNoLookupExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part // ExtractMapNoLookupExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
// refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the // refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the
// root yaml.Node pointer is used directly. Pass a true bit to includeExtensions to include extension keys in the map. // root yaml.Node pointer is used directly. Pass a true bit to includeExtensions to include extension keys in the map.
@@ -502,7 +484,7 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
root *yaml.Node, root *yaml.Node,
idx *index.SpecIndex, idx *index.SpecIndex,
includeExtensions bool, includeExtensions bool,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) { ) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]() valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]()
var circError error var circError error
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
@@ -540,10 +522,12 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
var isReference bool var isReference bool
var referenceValue string var referenceValue string
var refNode *yaml.Node
// if value is a reference, we have to look it up in the index! // if value is a reference, we have to look it up in the index!
if h, _, rv := utils.IsNodeRefValue(node); h { if h, _, rv := utils.IsNodeRefValue(node); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, node, idx) ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, node, idx)
if ref != nil { if ref != nil {
refNode = node
node = ref node = ref
isReference = true isReference = true
referenceValue = rv referenceValue = rv
@@ -570,19 +554,21 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
return nil, berr return nil, berr
} }
if isReference { if isReference {
SetReference(n, referenceValue) SetReference(n, referenceValue, refNode)
} }
if currentKey != nil { if currentKey != nil {
v := ValueReference[PT]{
Value: n,
ValueNode: node,
}
v.SetReference(referenceValue, refNode)
valueMap.Set( valueMap.Set(
KeyReference[string]{ KeyReference[string]{
Value: currentKey.Value, Value: currentKey.Value,
KeyNode: currentKey, KeyNode: currentKey,
}, },
ValueReference[PT]{ v,
Value: n,
ValueNode: node,
Reference: referenceValue,
},
) )
} }
} }
@@ -591,7 +577,6 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
return valueMap, circError return valueMap, circError
} }
return valueMap, nil return valueMap, nil
} }
// ExtractMapNoLookup will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part // ExtractMapNoLookup will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
@@ -603,7 +588,7 @@ func ExtractMapNoLookup[PT Buildable[N], N any](
ctx context.Context, ctx context.Context,
root *yaml.Node, root *yaml.Node,
idx *index.SpecIndex, idx *index.SpecIndex,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) { ) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
return ExtractMapNoLookupExtensions[PT, N](ctx, root, idx, false) return ExtractMapNoLookupExtensions[PT, N](ctx, root, idx, false)
} }
@@ -624,7 +609,186 @@ func ExtractMapExtensions[PT Buildable[N], N any](
root *yaml.Node, root *yaml.Node,
idx *index.SpecIndex, idx *index.SpecIndex,
extensions bool, extensions bool,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) { ) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
var labelNode, valueNode *yaml.Node
var circError error
root = utils.NodeAlias(root)
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref, fIdx, err, fCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
ctx = fCtx
idx = fIdx
if err != nil {
circError = err
}
} else {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
} else {
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
valueNode = utils.NodeAlias(valueNode)
if valueNode != nil {
if h, _, _ := utils.IsNodeRefValue(valueNode); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, valueNode, idx)
if ref != nil {
valueNode = ref
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
} else {
if err != nil {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
err.Error())
}
}
}
}
}
if valueNode != nil {
valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]()
type buildInput struct {
label *yaml.Node
value *yaml.Node
}
in := make(chan buildInput)
out := make(chan mappingResult[PT])
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(2) // input and output goroutines.
// TranslatePipeline input.
go func() {
defer func() {
close(in)
wg.Done()
}()
var currentLabelNode *yaml.Node
for i, en := range valueNode.Content {
if !extensions {
if strings.HasPrefix(en.Value, "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
}
en = utils.NodeAlias(en)
if i%2 == 0 {
currentLabelNode = en
continue
}
select {
case in <- buildInput{
label: currentLabelNode,
value: en,
}:
case <-done:
return
}
}
}()
// TranslatePipeline output.
go func() {
for {
result, ok := <-out
if !ok {
break
}
valueMap.Set(result.k, result.v)
}
close(done)
wg.Done()
}()
translateFunc := func(input buildInput) (mappingResult[PT], error) {
foundIndex := idx
foundContext := ctx
en := input.value
var refNode *yaml.Node
var referenceValue string
// check our valueNode isn't a reference still.
if h, _, refVal := utils.IsNodeRefValue(en); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, en, idx)
if ref != nil {
refNode = en
en = ref
referenceValue = refVal
if fIdx != nil {
foundIndex = fIdx
}
foundContext = nCtx
if err != nil {
circError = err
}
} else {
if err != nil {
return mappingResult[PT]{}, fmt.Errorf("flat map build failed: reference cannot be found: %s",
err.Error())
}
}
}
var n PT = new(N)
en = utils.NodeAlias(en)
_ = BuildModel(en, n)
err := n.Build(foundContext, input.label, en, foundIndex)
if err != nil {
return mappingResult[PT]{}, err
}
if referenceValue != "" {
SetReference(n, referenceValue, refNode)
}
v := ValueReference[PT]{
Value: n,
ValueNode: en,
}
v.SetReference(referenceValue, refNode)
return mappingResult[PT]{
k: KeyReference[string]{
KeyNode: input.label,
Value: input.label.Value,
},
v: v,
}, nil
}
err := datamodel.TranslatePipeline[buildInput, mappingResult[PT]](in, out, translateFunc)
wg.Wait()
if err != nil {
return nil, labelNode, valueNode, err
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return valueMap, labelNode, valueNode, circError
}
return valueMap, labelNode, valueNode, nil
}
return nil, labelNode, valueNode, nil
}
// ExtractMapExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'label' is
// used to locate the node to be extracted from the root node supplied. Supply a bit to decide if extensions should
// be included or not. required in some use cases.
//
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node.
func ExtractMapExtensionsOld[PT Buildable[N], N any](
ctx context.Context,
label string,
root *yaml.Node,
idx *index.SpecIndex,
extensions bool,
) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
var referenceValue string var referenceValue string
var labelNode, valueNode *yaml.Node var labelNode, valueNode *yaml.Node
var circError error var circError error
@@ -687,7 +851,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
} }
if ref != "" { if ref != "" {
SetReference(n, ref) SetReference(n, ref, nil)
} }
c <- mappingResult[PT]{ c <- mappingResult[PT]{
@@ -698,7 +862,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
v: ValueReference[PT]{ v: ValueReference[PT]{
Value: n, Value: n,
ValueNode: value, ValueNode: value,
Reference: ref, // Reference: ref,
}, },
} }
} }
@@ -773,7 +937,7 @@ func ExtractMap[PT Buildable[N], N any](
label string, label string,
root *yaml.Node, root *yaml.Node,
idx *index.SpecIndex, idx *index.SpecIndex,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) { ) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
return ExtractMapExtensions[PT, N](ctx, label, root, idx, false) return ExtractMapExtensions[PT, N](ctx, label, root, idx, false)
} }
@@ -781,7 +945,7 @@ func ExtractMap[PT Buildable[N], N any](
// //
// Maps // Maps
// //
// map[string]interface{} for maps // *orderedmap.Map[string, *yaml.Node] for maps
// //
// Slices // Slices
// //
@@ -790,54 +954,15 @@ func ExtractMap[PT Buildable[N], N any](
// int, float, bool, string // int, float, bool, string
// //
// int64, float64, bool, string // int64, float64, bool, string
func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] { func ExtractExtensions(root *yaml.Node) *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]] {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
extensions := utils.FindExtensionNodes(root.Content) extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[KeyReference[string]]ValueReference[any]) extensionMap := orderedmap.New[KeyReference[string], ValueReference[*yaml.Node]]()
for _, ext := range extensions { for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) { extensionMap.Set(KeyReference[string]{
var v interface{}
_ = ext.Value.Decode(&v)
extensionMap[KeyReference[string]{
Value: ext.Key.Value, Value: ext.Key.Value,
KeyNode: ext.Key, KeyNode: ext.Key,
}] = ValueReference[any]{Value: v, ValueNode: ext.Value} }, ValueReference[*yaml.Node]{Value: ext.Value, ValueNode: ext.Value})
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
_ = ext.Value.Decode(&v)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: v, ValueNode: ext.Value}
}
} }
return extensionMap return extensionMap
} }
@@ -869,6 +994,10 @@ func GenerateHashString(v any) string {
return fmt.Sprintf(HASH, h.Hash()) return fmt.Sprintf(HASH, h.Hash())
} }
} }
if n, ok := v.(*yaml.Node); ok {
b, _ := yaml.Marshal(n)
return fmt.Sprintf(HASH, sha256.Sum256(b))
}
// if we get here, we're a primitive, check if we're a pointer and de-point // if we get here, we're a primitive, check if we're a pointer and de-point
if reflect.TypeOf(v).Kind() == reflect.Ptr { if reflect.TypeOf(v).Kind() == reflect.Ptr {
v = reflect.ValueOf(v).Elem().Interface() v = reflect.ValueOf(v).Elem().Interface()
@@ -876,6 +1005,15 @@ func GenerateHashString(v any) string {
return fmt.Sprintf(HASH, sha256.Sum256([]byte(fmt.Sprint(v)))) return fmt.Sprintf(HASH, sha256.Sum256([]byte(fmt.Sprint(v))))
} }
func ValueToString(v any) string {
if n, ok := v.(*yaml.Node); ok {
b, _ := yaml.Marshal(n)
return string(b)
}
return fmt.Sprint(v)
}
// LocateRefEnd will perform a complete lookup for a $ref node. This function searches the entire index for // LocateRefEnd will perform a complete lookup for a $ref node. This function searches the entire index for
// the reference being supplied. If there is a match found, the reference *yaml.Node is returned. // the reference being supplied. If there is a match found, the reference *yaml.Node is returned.
// the function operates recursively and will keep iterating through references until it finds a non-reference // the function operates recursively and will keep iterating through references until it finds a non-reference

View File

@@ -7,51 +7,52 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestFindItemInMap(t *testing.T) { func TestFindItemInOrderedMap(t *testing.T) {
v := make(map[KeyReference[string]]ValueReference[string]) v := orderedmap.New[KeyReference[string], ValueReference[string]]()
v[KeyReference[string]{ v.Set(KeyReference[string]{
Value: "pizza", Value: "pizza",
}] = ValueReference[string]{ }, ValueReference[string]{
Value: "pie", Value: "pie",
} })
assert.Equal(t, "pie", FindItemInMap("pizza", v).Value) assert.Equal(t, "pie", FindItemInOrderedMap("pizza", v).Value)
} }
func TestFindItemInMap_WrongCase(t *testing.T) { func TestFindItemInOrderedMap_WrongCase(t *testing.T) {
v := make(map[KeyReference[string]]ValueReference[string]) v := orderedmap.New[KeyReference[string], ValueReference[string]]()
v[KeyReference[string]{ v.Set(KeyReference[string]{
Value: "pizza", Value: "pizza",
}] = ValueReference[string]{ }, ValueReference[string]{
Value: "pie", Value: "pie",
} })
assert.Equal(t, "pie", FindItemInMap("PIZZA", v).Value) assert.Equal(t, "pie", FindItemInOrderedMap("PIZZA", v).Value)
} }
func TestFindItemInMap_Error(t *testing.T) { func TestFindItemInOrderedMap_Error(t *testing.T) {
v := make(map[KeyReference[string]]ValueReference[string]) v := orderedmap.New[KeyReference[string], ValueReference[string]]()
v[KeyReference[string]{ v.Set(KeyReference[string]{
Value: "pizza", Value: "pizza",
}] = ValueReference[string]{ }, ValueReference[string]{
Value: "pie", Value: "pie",
} })
assert.Nil(t, FindItemInMap("nuggets", v)) assert.Nil(t, FindItemInOrderedMap("nuggets", v))
} }
func TestLocateRefNode(t *testing.T) { func TestLocateRefNode(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
cake: cake:
@@ -69,11 +70,9 @@ func TestLocateRefNode(t *testing.T) {
located, _, _ := LocateRefNode(cNode.Content[0], idx) located, _, _ := LocateRefNode(cNode.Content[0], idx)
assert.NotNil(t, located) assert.NotNil(t, located)
} }
func TestLocateRefNode_BadNode(t *testing.T) { func TestLocateRefNode_BadNode(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
cake: cake:
@@ -94,11 +93,9 @@ func TestLocateRefNode_BadNode(t *testing.T) {
// should both be empty. // should both be empty.
assert.Nil(t, located) assert.Nil(t, located)
assert.Nil(t, err) assert.Nil(t, err)
} }
func TestLocateRefNode_Path(t *testing.T) { func TestLocateRefNode_Path(t *testing.T) {
yml := `paths: yml := `paths:
/burger/time: /burger/time:
description: hello` description: hello`
@@ -115,11 +112,9 @@ func TestLocateRefNode_Path(t *testing.T) {
located, _, _ := LocateRefNode(cNode.Content[0], idx) located, _, _ := LocateRefNode(cNode.Content[0], idx)
assert.NotNil(t, located) assert.NotNil(t, located)
} }
func TestLocateRefNode_Path_NotFound(t *testing.T) { func TestLocateRefNode_Path_NotFound(t *testing.T) {
yml := `paths: yml := `paths:
/burger/time: /burger/time:
description: hello` description: hello`
@@ -137,7 +132,6 @@ func TestLocateRefNode_Path_NotFound(t *testing.T) {
located, _, err := LocateRefNode(cNode.Content[0], idx) located, _, err := LocateRefNode(cNode.Content[0], idx)
assert.Nil(t, located) assert.Nil(t, located)
assert.Error(t, err) assert.Error(t, err)
} }
type pizza struct { type pizza struct {
@@ -149,7 +143,6 @@ func (p *pizza) Build(_ context.Context, _, _ *yaml.Node, _ *index.SpecIndex) er
} }
func TestExtractObject(t *testing.T) { func TestExtractObject(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -173,7 +166,6 @@ func TestExtractObject(t *testing.T) {
} }
func TestExtractObject_Ref(t *testing.T) { func TestExtractObject_Ref(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -197,7 +189,6 @@ func TestExtractObject_Ref(t *testing.T) {
} }
func TestExtractObject_DoubleRef(t *testing.T) { func TestExtractObject_DoubleRef(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
cake: cake:
@@ -282,7 +273,6 @@ func TestExtractObject_DoubleRef_Circular_Fail(t *testing.T) {
} }
func TestExtractObject_DoubleRef_Circular_Direct(t *testing.T) { func TestExtractObject_DoubleRef_Circular_Direct(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
loopy: loopy:
@@ -312,7 +302,6 @@ func TestExtractObject_DoubleRef_Circular_Direct(t *testing.T) {
} }
func TestExtractObject_DoubleRef_Circular_Direct_Fail(t *testing.T) { func TestExtractObject_DoubleRef_Circular_Direct_Fail(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
loopy: loopy:
@@ -338,7 +327,6 @@ func TestExtractObject_DoubleRef_Circular_Direct_Fail(t *testing.T) {
_, err := ExtractObject[*pizza](context.Background(), "tags", cNode.Content[0], idx) _, err := ExtractObject[*pizza](context.Background(), "tags", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
type test_borked struct { type test_borked struct {
@@ -374,7 +362,6 @@ func (t *test_Good) Build(_ context.Context, _, root *yaml.Node, idx *index.Spec
} }
func TestExtractObject_BadLowLevelModel(t *testing.T) { func TestExtractObject_BadLowLevelModel(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
hey:` hey:`
@@ -391,11 +378,9 @@ func TestExtractObject_BadLowLevelModel(t *testing.T) {
_, err := ExtractObject[*test_noGood](context.Background(), "thing", &cNode, idx) _, err := ExtractObject[*test_noGood](context.Background(), "thing", &cNode, idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestExtractObject_BadBuild(t *testing.T) { func TestExtractObject_BadBuild(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
hey:` hey:`
@@ -412,11 +397,9 @@ func TestExtractObject_BadBuild(t *testing.T) {
_, err := ExtractObject[*test_almostGood](context.Background(), "thing", &cNode, idx) _, err := ExtractObject[*test_almostGood](context.Background(), "thing", &cNode, idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestExtractObject_BadLabel(t *testing.T) { func TestExtractObject_BadLabel(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
hey:` hey:`
@@ -434,11 +417,9 @@ func TestExtractObject_BadLabel(t *testing.T) {
res, err := ExtractObject[*test_almostGood](context.Background(), "ding", &cNode, idx) res, err := ExtractObject[*test_almostGood](context.Background(), "ding", &cNode, idx)
assert.Nil(t, res.Value) assert.Nil(t, res.Value)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestExtractObject_PathIsCircular(t *testing.T) { func TestExtractObject_PathIsCircular(t *testing.T) {
// first we need an index. // first we need an index.
yml := `paths: yml := `paths:
'/something/here': '/something/here':
@@ -467,11 +448,9 @@ func TestExtractObject_PathIsCircular(t *testing.T) {
res, err := ExtractObject[*test_Good](context.Background(), "thing", &rootNode, idx) res, err := ExtractObject[*test_Good](context.Background(), "thing", &rootNode, idx)
assert.NotNil(t, res.Value) assert.NotNil(t, res.Value)
assert.Error(t, err) // circular error would have been thrown. assert.Error(t, err) // circular error would have been thrown.
} }
func TestExtractObject_PathIsCircular_IgnoreErrors(t *testing.T) { func TestExtractObject_PathIsCircular_IgnoreErrors(t *testing.T) {
// first we need an index. // first we need an index.
yml := `paths: yml := `paths:
'/something/here': '/something/here':
@@ -503,11 +482,9 @@ func TestExtractObject_PathIsCircular_IgnoreErrors(t *testing.T) {
res, err := ExtractObject[*test_Good](context.Background(), "thing", &rootNode, idx) res, err := ExtractObject[*test_Good](context.Background(), "thing", &rootNode, idx)
assert.NotNil(t, res.Value) assert.NotNil(t, res.Value)
assert.NoError(t, err) // circular error would have been thrown, but we're ignoring them. assert.NoError(t, err) // circular error would have been thrown, but we're ignoring them.
} }
func TestExtractObjectRaw(t *testing.T) { func TestExtractObjectRaw(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -530,7 +507,6 @@ func TestExtractObjectRaw(t *testing.T) {
} }
func TestExtractObjectRaw_With_Ref(t *testing.T) { func TestExtractObjectRaw_With_Ref(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -555,7 +531,6 @@ func TestExtractObjectRaw_With_Ref(t *testing.T) {
} }
func TestExtractObjectRaw_Ref_Circular(t *testing.T) { func TestExtractObjectRaw_Ref_Circular(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -579,11 +554,9 @@ func TestExtractObjectRaw_Ref_Circular(t *testing.T) {
tag, err, _, _ := ExtractObjectRaw[*pizza](context.Background(), nil, cNode.Content[0], idx) tag, err, _, _ := ExtractObjectRaw[*pizza](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.NotNil(t, tag) assert.NotNil(t, tag)
} }
func TestExtractObjectRaw_RefBroken(t *testing.T) { func TestExtractObjectRaw_RefBroken(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -601,11 +574,9 @@ func TestExtractObjectRaw_RefBroken(t *testing.T) {
tag, err, _, _ := ExtractObjectRaw[*pizza](context.Background(), nil, cNode.Content[0], idx) tag, err, _, _ := ExtractObjectRaw[*pizza](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, tag) assert.Nil(t, tag)
} }
func TestExtractObjectRaw_Ref_NonBuildable(t *testing.T) { func TestExtractObjectRaw_Ref_NonBuildable(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -622,11 +593,9 @@ func TestExtractObjectRaw_Ref_NonBuildable(t *testing.T) {
_, err, _, _ := ExtractObjectRaw[*test_noGood](context.Background(), nil, cNode.Content[0], idx) _, err, _, _ := ExtractObjectRaw[*test_noGood](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestExtractObjectRaw_Ref_AlmostBuildable(t *testing.T) { func TestExtractObjectRaw_Ref_AlmostBuildable(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -643,11 +612,9 @@ func TestExtractObjectRaw_Ref_AlmostBuildable(t *testing.T) {
_, err, _, _ := ExtractObjectRaw[*test_almostGood](context.Background(), nil, cNode.Content[0], idx) _, err, _, _ := ExtractObjectRaw[*test_almostGood](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestExtractArray(t *testing.T) { func TestExtractArray(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -675,7 +642,6 @@ func TestExtractArray(t *testing.T) {
} }
func TestExtractArray_Ref(t *testing.T) { func TestExtractArray_Ref(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
things: things:
@@ -702,7 +668,6 @@ func TestExtractArray_Ref(t *testing.T) {
} }
func TestExtractArray_Ref_Unbuildable(t *testing.T) { func TestExtractArray_Ref_Unbuildable(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
things: things:
@@ -726,7 +691,6 @@ func TestExtractArray_Ref_Unbuildable(t *testing.T) {
} }
func TestExtractArray_Ref_Circular(t *testing.T) { func TestExtractArray_Ref_Circular(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -754,7 +718,6 @@ func TestExtractArray_Ref_Circular(t *testing.T) {
} }
func TestExtractArray_Ref_Bad(t *testing.T) { func TestExtractArray_Ref_Bad(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -782,7 +745,6 @@ func TestExtractArray_Ref_Bad(t *testing.T) {
} }
func TestExtractArray_Ref_Nested(t *testing.T) { func TestExtractArray_Ref_Nested(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -811,7 +773,6 @@ func TestExtractArray_Ref_Nested(t *testing.T) {
} }
func TestExtractArray_Ref_Nested_Circular(t *testing.T) { func TestExtractArray_Ref_Nested_Circular(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -840,7 +801,6 @@ func TestExtractArray_Ref_Nested_Circular(t *testing.T) {
} }
func TestExtractArray_Ref_Nested_BadRef(t *testing.T) { func TestExtractArray_Ref_Nested_BadRef(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -867,7 +827,6 @@ func TestExtractArray_Ref_Nested_BadRef(t *testing.T) {
} }
func TestExtractArray_Ref_Nested_CircularFlat(t *testing.T) { func TestExtractArray_Ref_Nested_CircularFlat(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -896,7 +855,6 @@ func TestExtractArray_Ref_Nested_CircularFlat(t *testing.T) {
} }
func TestExtractArray_BadBuild(t *testing.T) { func TestExtractArray_BadBuild(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs:` thongs:`
@@ -918,7 +876,6 @@ func TestExtractArray_BadBuild(t *testing.T) {
} }
func TestExtractArray_BadRefPropsTupe(t *testing.T) { func TestExtractArray_BadRefPropsTupe(t *testing.T) {
yml := `components: yml := `components:
parameters: parameters:
cakes: cakes:
@@ -940,45 +897,7 @@ func TestExtractArray_BadRefPropsTupe(t *testing.T) {
assert.Len(t, things, 0) assert.Len(t, things, 0)
} }
func TestExtractExample_String(t *testing.T) {
yml := `hi`
var e yaml.Node
_ = yaml.Unmarshal([]byte(yml), &e)
exp := ExtractExample(e.Content[0], e.Content[0])
assert.NotNil(t, exp.Value)
assert.Equal(t, "hi", exp.Value)
}
func TestExtractExample_Map(t *testing.T) {
yml := `one: two`
var e yaml.Node
_ = yaml.Unmarshal([]byte(yml), &e)
exp := ExtractExample(e.Content[0], e.Content[0])
assert.NotNil(t, exp.Value)
if n, ok := exp.Value.(map[string]interface{}); ok {
assert.Equal(t, "two", n["one"])
} else {
panic("example unpacked incorrectly.")
}
}
func TestExtractExample_Array(t *testing.T) {
yml := `- hello`
var e yaml.Node
_ = yaml.Unmarshal([]byte(yml), &e)
exp := ExtractExample(e.Content[0], e.Content[0])
assert.NotNil(t, exp.Value)
if n, ok := exp.Value.([]interface{}); ok {
assert.Equal(t, "hello", n[0])
} else {
panic("example unpacked incorrectly.")
}
}
func TestExtractMapFlatNoLookup(t *testing.T) { func TestExtractMapFlatNoLookup(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -997,11 +916,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(things)) assert.Equal(t, 1, orderedmap.Len(things))
} }
func TestExtractMap_NoLookupWithExtensions(t *testing.T) { func TestExtractMap_NoLookupWithExtensions(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -1031,7 +948,6 @@ one:
} }
func TestExtractMap_NoLookupWithExtensions_UsingMerge(t *testing.T) { func TestExtractMap_NoLookupWithExtensions_UsingMerge(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -1053,11 +969,9 @@ one:
things, err := ExtractMapNoLookupExtensions[*test_Good](context.Background(), cNode.Content[0], idx, true) things, err := ExtractMapNoLookupExtensions[*test_Good](context.Background(), cNode.Content[0], idx, true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 4, orderedmap.Len(things)) assert.Equal(t, 4, orderedmap.Len(things))
} }
func TestExtractMap_NoLookupWithoutExtensions(t *testing.T) { func TestExtractMap_NoLookupWithoutExtensions(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -1083,7 +997,6 @@ one:
} }
func TestExtractMap_WithExtensions(t *testing.T) { func TestExtractMap_WithExtensions(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -1105,7 +1018,6 @@ one:
} }
func TestExtractMap_WithoutExtensions(t *testing.T) { func TestExtractMap_WithoutExtensions(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -1127,7 +1039,6 @@ one:
} }
func TestExtractMapFlatNoLookup_Ref(t *testing.T) { func TestExtractMapFlatNoLookup_Ref(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -1149,11 +1060,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(things)) assert.Equal(t, 1, orderedmap.Len(things))
} }
func TestExtractMapFlatNoLookup_Ref_Bad(t *testing.T) { func TestExtractMapFlatNoLookup_Ref_Bad(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -1175,11 +1084,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things)) assert.Zero(t, orderedmap.Len(things))
} }
func TestExtractMapFlatNoLookup_Ref_Circular(t *testing.T) { func TestExtractMapFlatNoLookup_Ref_Circular(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
thongs: thongs:
@@ -1207,11 +1114,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 1, orderedmap.Len(things)) assert.Equal(t, 1, orderedmap.Len(things))
} }
func TestExtractMapFlatNoLookup_Ref_BadBuild(t *testing.T) { func TestExtractMapFlatNoLookup_Ref_BadBuild(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -1233,11 +1138,9 @@ hello:
things, err := ExtractMapNoLookup[*test_noGood](context.Background(), cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_noGood](context.Background(), cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things)) assert.Zero(t, orderedmap.Len(things))
} }
func TestExtractMapFlatNoLookup_Ref_AlmostBuild(t *testing.T) { func TestExtractMapFlatNoLookup_Ref_AlmostBuild(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
pizza: pizza:
@@ -1259,11 +1162,9 @@ one:
things, err := ExtractMapNoLookup[*test_almostGood](context.Background(), cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_almostGood](context.Background(), cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things)) assert.Zero(t, orderedmap.Len(things))
} }
func TestExtractMapFlat(t *testing.T) { func TestExtractMapFlat(t *testing.T) {
yml := `components:` yml := `components:`
var idxNode yaml.Node var idxNode yaml.Node
@@ -1282,11 +1183,9 @@ one:
things, _, _, err := ExtractMap[*test_Good](context.Background(), "one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good](context.Background(), "one", cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(things)) assert.Equal(t, 1, orderedmap.Len(things))
} }
func TestExtractMapFlat_Ref(t *testing.T) { func TestExtractMapFlat_Ref(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stank: stank:
@@ -1313,11 +1212,9 @@ one:
for pair := orderedmap.First(things); pair != nil; pair = pair.Next() { for pair := orderedmap.First(things); pair != nil; pair = pair.Next() {
assert.Equal(t, 99, pair.Value().Value.AlmostWork.Value) assert.Equal(t, 99, pair.Value().Value.AlmostWork.Value)
} }
} }
func TestExtractMapFlat_DoubleRef(t *testing.T) { func TestExtractMapFlat_DoubleRef(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stank: stank:
@@ -1346,7 +1243,6 @@ func TestExtractMapFlat_DoubleRef(t *testing.T) {
} }
func TestExtractMapFlat_DoubleRef_Error(t *testing.T) { func TestExtractMapFlat_DoubleRef_Error(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stank: stank:
@@ -1369,11 +1265,9 @@ func TestExtractMapFlat_DoubleRef_Error(t *testing.T) {
things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things)) assert.Zero(t, orderedmap.Len(things))
} }
func TestExtractMapFlat_DoubleRef_Error_NotFound(t *testing.T) { func TestExtractMapFlat_DoubleRef_Error_NotFound(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stank: stank:
@@ -1396,11 +1290,9 @@ func TestExtractMapFlat_DoubleRef_Error_NotFound(t *testing.T) {
things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things)) assert.Zero(t, orderedmap.Len(things))
} }
func TestExtractMapFlat_DoubleRef_Circles(t *testing.T) { func TestExtractMapFlat_DoubleRef_Circles(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stonk: stonk:
@@ -1428,11 +1320,9 @@ func TestExtractMapFlat_DoubleRef_Circles(t *testing.T) {
things, _, _, err := ExtractMap[*test_Good](context.Background(), "one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 1, orderedmap.Len(things)) assert.Equal(t, 1, orderedmap.Len(things))
} }
func TestExtractMapFlat_Ref_Error(t *testing.T) { func TestExtractMapFlat_Ref_Error(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stank: stank:
@@ -1455,11 +1345,9 @@ func TestExtractMapFlat_Ref_Error(t *testing.T) {
things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things)) assert.Zero(t, orderedmap.Len(things))
} }
func TestExtractMapFlat_Ref_Circ_Error(t *testing.T) { func TestExtractMapFlat_Ref_Circ_Error(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stink: stink:
@@ -1488,7 +1376,6 @@ func TestExtractMapFlat_Ref_Circ_Error(t *testing.T) {
} }
func TestExtractMapFlat_Ref_Nested_Circ_Error(t *testing.T) { func TestExtractMapFlat_Ref_Nested_Circ_Error(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stink: stink:
@@ -1518,7 +1405,6 @@ func TestExtractMapFlat_Ref_Nested_Circ_Error(t *testing.T) {
} }
func TestExtractMapFlat_Ref_Nested_Error(t *testing.T) { func TestExtractMapFlat_Ref_Nested_Error(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stink: stink:
@@ -1544,7 +1430,6 @@ func TestExtractMapFlat_Ref_Nested_Error(t *testing.T) {
} }
func TestExtractMapFlat_BadKey_Ref_Nested_Error(t *testing.T) { func TestExtractMapFlat_BadKey_Ref_Nested_Error(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stink: stink:
@@ -1570,7 +1455,6 @@ func TestExtractMapFlat_BadKey_Ref_Nested_Error(t *testing.T) {
} }
func TestExtractMapFlat_Ref_Bad(t *testing.T) { func TestExtractMapFlat_Ref_Bad(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
stink: stink:
@@ -1599,7 +1483,6 @@ func TestExtractMapFlat_Ref_Bad(t *testing.T) {
} }
func TestExtractExtensions(t *testing.T) { func TestExtractExtensions(t *testing.T) {
yml := `x-bing: ding yml := `x-bing: ding
x-bong: 1 x-bong: 1
x-ling: true x-ling: true
@@ -1612,25 +1495,27 @@ x-tacos: [1,2,3]`
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
r := ExtractExtensions(idxNode.Content[0]) r := ExtractExtensions(idxNode.Content[0])
assert.Len(t, r, 6) assert.Equal(t, 6, orderedmap.Len(r))
for i := range r { for pair := orderedmap.First(r); pair != nil; pair = pair.Next() {
switch i.Value { var v any
_ = pair.Value().Value.Decode(&v)
switch pair.Key().Value {
case "x-bing": case "x-bing":
assert.Equal(t, "ding", r[i].Value) assert.Equal(t, "ding", v)
case "x-bong": case "x-bong":
assert.Equal(t, int64(1), r[i].Value) assert.Equal(t, 1, v)
case "x-ling": case "x-ling":
assert.Equal(t, true, r[i].Value) assert.Equal(t, true, v)
case "x-long": case "x-long":
assert.Equal(t, 0.99, r[i].Value) assert.Equal(t, 0.99, v)
case "x-fish": case "x-fish":
if a, ok := r[i].Value.(map[string]interface{}); ok { var m map[string]any
assert.Equal(t, "yeah", a["woo"]) err := pair.Value().Value.Decode(&m)
} else { require.NoError(t, err)
panic("should not fail casting") assert.Equal(t, "yeah", m["woo"])
}
case "x-tacos": case "x-tacos":
assert.Len(t, r[i].Value, 3) assert.Len(t, v, 3)
} }
} }
} }
@@ -1650,8 +1535,8 @@ func (f test_fresh) Hash() [32]byte {
} }
return sha256.Sum256([]byte(strings.Join(data, "|"))) return sha256.Sum256([]byte(strings.Join(data, "|")))
} }
func TestAreEqual(t *testing.T) {
func TestAreEqual(t *testing.T) {
var hey *test_fresh var hey *test_fresh
assert.True(t, AreEqual(test_fresh{val: "hello"}, test_fresh{val: "hello"})) assert.True(t, AreEqual(test_fresh{val: "hello"}, test_fresh{val: "hello"}))
@@ -1664,7 +1549,6 @@ func TestAreEqual(t *testing.T) {
} }
func TestGenerateHashString(t *testing.T) { func TestGenerateHashString(t *testing.T) {
assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
GenerateHashString(test_fresh{val: "hello"})) GenerateHashString(test_fresh{val: "hello"}))
@@ -1676,46 +1560,39 @@ func TestGenerateHashString(t *testing.T) {
assert.Equal(t, "", assert.Equal(t, "",
GenerateHashString(nil)) GenerateHashString(nil))
} }
func TestGenerateHashString_Pointer(t *testing.T) { func TestGenerateHashString_Pointer(t *testing.T) {
val := true val := true
assert.Equal(t, "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b", assert.Equal(t, "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b",
GenerateHashString(test_fresh{thang: &val})) GenerateHashString(test_fresh{thang: &val}))
assert.Equal(t, "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b", assert.Equal(t, "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b",
GenerateHashString(&val)) GenerateHashString(&val))
} }
func TestSetReference(t *testing.T) { func TestSetReference(t *testing.T) {
type testObj struct { type testObj struct {
*Reference *Reference
} }
n := testObj{Reference: &Reference{}} n := testObj{Reference: &Reference{}}
SetReference(&n, "#/pigeon/street") SetReference(&n, "#/pigeon/street", nil)
assert.Equal(t, "#/pigeon/street", n.GetReference()) assert.Equal(t, "#/pigeon/street", n.GetReference())
} }
func TestSetReference_nil(t *testing.T) { func TestSetReference_nil(t *testing.T) {
type testObj struct { type testObj struct {
*Reference *Reference
} }
n := testObj{Reference: &Reference{}} n := testObj{Reference: &Reference{}}
SetReference(nil, "#/pigeon/street") SetReference(nil, "#/pigeon/street", nil)
assert.NotEqual(t, "#/pigeon/street", n.GetReference()) assert.NotEqual(t, "#/pigeon/street", n.GetReference())
} }
func TestLocateRefNode_CurrentPathKey_HttpLink(t *testing.T) { func TestLocateRefNode_CurrentPathKey_HttpLink(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1741,7 +1618,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink(t *testing.T) {
} }
func TestLocateRefNode_CurrentPathKey_HttpLink_Local(t *testing.T) { func TestLocateRefNode_CurrentPathKey_HttpLink_Local(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1767,7 +1643,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink_Local(t *testing.T) {
} }
func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx(t *testing.T) { func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1792,7 +1667,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx(t *testing.T) {
} }
func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx_WithPath(t *testing.T) { func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx_WithPath(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1817,7 +1691,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx_WithPath(t *testing.T)
} }
func TestLocateRefNode_CurrentPathKey_Path_Link(t *testing.T) { func TestLocateRefNode_CurrentPathKey_Path_Link(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1842,7 +1715,6 @@ func TestLocateRefNode_CurrentPathKey_Path_Link(t *testing.T) {
} }
func TestLocateRefNode_CurrentPathKey_Path_URL(t *testing.T) { func TestLocateRefNode_CurrentPathKey_Path_URL(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1869,7 +1741,6 @@ func TestLocateRefNode_CurrentPathKey_Path_URL(t *testing.T) {
} }
func TestLocateRefNode_CurrentPathKey_DeeperPath_URL(t *testing.T) { func TestLocateRefNode_CurrentPathKey_DeeperPath_URL(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1896,7 +1767,6 @@ func TestLocateRefNode_CurrentPathKey_DeeperPath_URL(t *testing.T) {
} }
func TestLocateRefNode_NoExplode(t *testing.T) { func TestLocateRefNode_NoExplode(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1923,7 +1793,6 @@ func TestLocateRefNode_NoExplode(t *testing.T) {
} }
func TestLocateRefNode_NoExplode_HTTP(t *testing.T) { func TestLocateRefNode_NoExplode_HTTP(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1951,7 +1820,6 @@ func TestLocateRefNode_NoExplode_HTTP(t *testing.T) {
} }
func TestLocateRefNode_NoExplode_NoSpecPath(t *testing.T) { func TestLocateRefNode_NoExplode_NoSpecPath(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -1979,7 +1847,6 @@ func TestLocateRefNode_NoExplode_NoSpecPath(t *testing.T) {
} }
func TestLocateRefNode_DoARealLookup(t *testing.T) { func TestLocateRefNode_DoARealLookup(t *testing.T) {
no := yaml.Node{ no := yaml.Node{
Kind: yaml.MappingNode, Kind: yaml.MappingNode,
Content: []*yaml.Node{ Content: []*yaml.Node{
@@ -2020,7 +1887,6 @@ func TestLocateRefNode_DoARealLookup(t *testing.T) {
} }
func TestLocateRefEndNoRef_NoName(t *testing.T) { func TestLocateRefEndNoRef_NoName(t *testing.T) {
r := &yaml.Node{Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "$ref"}, {Kind: yaml.ScalarNode, Value: ""}}} r := &yaml.Node{Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "$ref"}, {Kind: yaml.ScalarNode, Value: ""}}}
n, i, e, c := LocateRefEnd(nil, r, nil, 0) n, i, e, c := LocateRefEnd(nil, r, nil, 0)
assert.Nil(t, n) assert.Nil(t, n)
@@ -2030,7 +1896,6 @@ func TestLocateRefEndNoRef_NoName(t *testing.T) {
} }
func TestLocateRefEndNoRef(t *testing.T) { func TestLocateRefEndNoRef(t *testing.T) {
r := &yaml.Node{Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "$ref"}, {Kind: yaml.ScalarNode, Value: "cake"}}} r := &yaml.Node{Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "$ref"}, {Kind: yaml.ScalarNode, Value: "cake"}}}
n, i, e, c := LocateRefEnd(context.Background(), r, index.NewSpecIndexWithConfig(r, index.CreateClosedAPIIndexConfig()), 0) n, i, e, c := LocateRefEnd(context.Background(), r, index.NewSpecIndexWithConfig(r, index.CreateClosedAPIIndexConfig()), 0)
assert.Nil(t, n) assert.Nil(t, n)
@@ -2049,7 +1914,6 @@ func TestLocateRefEnd_TooDeep(t *testing.T) {
} }
func TestLocateRefEnd_Loop(t *testing.T) { func TestLocateRefEnd_Loop(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml") yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn) _ = yaml.Unmarshal(yml, &bsn)
@@ -2094,7 +1958,6 @@ func TestLocateRefEnd_Loop(t *testing.T) {
} }
func TestLocateRefEnd_Loop_WithResolve(t *testing.T) { func TestLocateRefEnd_Loop_WithResolve(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml") yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn) _ = yaml.Unmarshal(yml, &bsn)
@@ -2139,7 +2002,6 @@ func TestLocateRefEnd_Loop_WithResolve(t *testing.T) {
} }
func TestLocateRefEnd_Empty(t *testing.T) { func TestLocateRefEnd_Empty(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml") yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn) _ = yaml.Unmarshal(yml, &bsn)
@@ -2184,7 +2046,6 @@ func TestLocateRefEnd_Empty(t *testing.T) {
} }
func TestArray_NotRefNotArray(t *testing.T) { func TestArray_NotRefNotArray(t *testing.T) {
yml := `` yml := ``
var idxNode yaml.Node var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
@@ -2201,5 +2062,4 @@ func TestArray_NotRefNotArray(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, err.Error(), "array build failed, input is not an array, line 2, column 3") assert.Equal(t, err.Error(), "array build failed, input is not an array, line 2, column 3")
assert.Len(t, things, 0) assert.Len(t, things, 0)
} }

View File

@@ -78,72 +78,63 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
switch field.Type() { switch field.Type() {
case reflect.TypeOf(map[string]NodeReference[any]{}): case reflect.TypeOf(orderedmap.New[string, NodeReference[*yaml.Node]]()):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[string]NodeReference[any]) items := orderedmap.New[string, NodeReference[*yaml.Node]]()
var currentLabel string var currentLabel string
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = sliceItem.Value currentLabel = sliceItem.Value
continue continue
} }
var decoded map[string]interface{} items.Set(currentLabel, NodeReference[*yaml.Node]{
// I cannot think of a way to make this error out by this point. Value: sliceItem,
_ = sliceItem.Decode(&decoded)
items[currentLabel] = NodeReference[any]{
Value: decoded,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
} })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf(map[string]NodeReference[string]{}): case reflect.TypeOf(orderedmap.New[string, NodeReference[string]]()):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[string]NodeReference[string]) items := orderedmap.New[string, NodeReference[string]]()
var currentLabel string var currentLabel string
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = sliceItem.Value currentLabel = sliceItem.Value
continue continue
} }
items[currentLabel] = NodeReference[string]{ items.Set(currentLabel, NodeReference[string]{
Value: fmt.Sprintf("%v", sliceItem.Value), Value: fmt.Sprintf("%v", sliceItem.Value),
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
} })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf(NodeReference[any]{}): case reflect.TypeOf(NodeReference[*yaml.Node]{}):
var decoded interface{}
_ = valueNode.Decode(&decoded)
if field.CanSet() { if field.CanSet() {
or := NodeReference[any]{Value: decoded, ValueNode: valueNode, KeyNode: keyNode} or := NodeReference[*yaml.Node]{Value: valueNode, ValueNode: valueNode, KeyNode: keyNode}
field.Set(reflect.ValueOf(or)) field.Set(reflect.ValueOf(or))
} }
case reflect.TypeOf([]NodeReference[any]{}): case reflect.TypeOf([]NodeReference[*yaml.Node]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[any] var items []NodeReference[*yaml.Node]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
var decoded map[string]interface{} items = append(items, NodeReference[*yaml.Node]{
err := sliceItem.Decode(&decoded) Value: sliceItem,
if err != nil {
return err
}
items = append(items, NodeReference[any]{
Value: decoded,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
@@ -341,56 +332,8 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
} }
// helper for unpacking string maps. // helper for unpacking string maps.
case reflect.TypeOf(map[KeyReference[string]]ValueReference[string]{}): case reflect.TypeOf(orderedmap.New[KeyReference[string], ValueReference[string]]()):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items[KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}] = ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
}
}
field.Set(reflect.ValueOf(items))
}
}
case reflect.TypeOf(KeyReference[map[KeyReference[string]]ValueReference[string]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items[KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}] = ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
}
}
ref := KeyReference[map[KeyReference[string]]ValueReference[string]]{
Value: items,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(ref))
}
}
case reflect.TypeOf(NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]{}):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := orderedmap.New[KeyReference[string], ValueReference[string]]() items := orderedmap.New[KeyReference[string], ValueReference[string]]()
@@ -408,7 +351,55 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
ValueNode: sliceItem, ValueNode: sliceItem,
}) })
} }
ref := NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]{ field.Set(reflect.ValueOf(items))
}
}
case reflect.TypeOf(KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := orderedmap.New[KeyReference[string], ValueReference[string]]()
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items.Set(KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}, ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
})
}
ref := KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{
Value: items,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(ref))
}
}
case reflect.TypeOf(NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := orderedmap.New[KeyReference[string], ValueReference[string]]()
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items.Set(KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}, ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
})
}
ref := NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{
Value: items, Value: items,
KeyNode: keyNode, KeyNode: keyNode,
ValueNode: valueNode, ValueNode: valueNode,
@@ -436,34 +427,18 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
} }
} }
case reflect.TypeOf(NodeReference[[]ValueReference[any]]{}): case reflect.TypeOf(NodeReference[[]ValueReference[*yaml.Node]]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []ValueReference[any] var items []ValueReference[*yaml.Node]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
items = append(items, ValueReference[*yaml.Node]{
var val any Value: sliceItem,
if utils.IsNodeIntValue(sliceItem) || utils.IsNodeFloatValue(sliceItem) {
if utils.IsNodeIntValue(sliceItem) {
val, _ = strconv.ParseInt(sliceItem.Value, 10, 64)
} else {
val, _ = strconv.ParseFloat(sliceItem.Value, 64)
}
}
if utils.IsNodeBoolValue(sliceItem) {
val, _ = strconv.ParseBool(sliceItem.Value)
}
if utils.IsNodeStringValue(sliceItem) {
val = sliceItem.Value
}
items = append(items, ValueReference[any]{
Value: val,
ValueNode: sliceItem, ValueNode: sliceItem,
}) })
} }
n := NodeReference[[]ValueReference[any]]{ n := NodeReference[[]ValueReference[*yaml.Node]]{
Value: items, Value: items,
KeyNode: keyNode, KeyNode: keyNode,
ValueNode: valueNode, ValueNode: valueNode,

View File

@@ -25,11 +25,11 @@ type hotdog struct {
Temps []NodeReference[int] Temps []NodeReference[int]
HighTemps []NodeReference[int64] HighTemps []NodeReference[int64]
Buns []NodeReference[bool] Buns []NodeReference[bool]
UnknownElements NodeReference[any] UnknownElements NodeReference[*yaml.Node]
LotsOfUnknowns []NodeReference[any] LotsOfUnknowns []NodeReference[*yaml.Node]
Where map[string]NodeReference[any] Where *orderedmap.Map[string, NodeReference[*yaml.Node]]
There map[string]NodeReference[string] There *orderedmap.Map[string, NodeReference[string]]
AllTheThings NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]] AllTheThings NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]
} }
func TestBuildModel_Mismatch(t *testing.T) { func TestBuildModel_Mismatch(t *testing.T) {
@@ -126,11 +126,15 @@ allTheThings:
assert.Equal(t, int64(7392837462032342), hd.MaxTempHigh.Value) assert.Equal(t, int64(7392837462032342), hd.MaxTempHigh.Value)
assert.Equal(t, 2, hd.Temps[1].Value) assert.Equal(t, 2, hd.Temps[1].Value)
assert.Equal(t, 27, hd.Temps[1].ValueNode.Line) assert.Equal(t, 27, hd.Temps[1].ValueNode.Line)
assert.Len(t, hd.UnknownElements.Value, 2)
var unknownElements map[string]any
_ = hd.UnknownElements.Value.Decode(&unknownElements)
assert.Len(t, unknownElements, 2)
assert.Len(t, hd.LotsOfUnknowns, 3) assert.Len(t, hd.LotsOfUnknowns, 3)
assert.Len(t, hd.Where, 2) assert.Equal(t, 2, orderedmap.Len(hd.Where))
assert.Len(t, hd.There, 2) assert.Equal(t, 2, orderedmap.Len(hd.There))
assert.Equal(t, "bear", hd.There["care"].Value) assert.Equal(t, "bear", hd.There.GetOrZero("care").Value)
assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value) assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value)
allTheThings := hd.AllTheThings.Value allTheThings := hd.AllTheThings.Value
@@ -201,27 +205,9 @@ thing: yeah`
assert.Equal(t, "yeah", ins.Thing.Value) assert.Equal(t, "yeah", ins.Thing.Value)
} }
func TestSetField_NodeRefAny_Error(t *testing.T) {
type internal struct {
Thing []NodeReference[any]
}
yml := `thing:
- 999
- false`
ins := new(internal)
var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins)
assert.Error(t, try)
}
func TestSetField_MapHelperWrapped(t *testing.T) { func TestSetField_MapHelperWrapped(t *testing.T) {
type internal struct { type internal struct {
Thing KeyReference[map[KeyReference[string]]ValueReference[string]] Thing KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]
} }
yml := `thing: yml := `thing:
@@ -236,12 +222,12 @@ func TestSetField_MapHelperWrapped(t *testing.T) {
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Len(t, ins.Thing.Value, 3) assert.Equal(t, 3, orderedmap.Len(ins.Thing.Value))
} }
func TestSetField_MapHelper(t *testing.T) { func TestSetField_MapHelper(t *testing.T) {
type internal struct { type internal struct {
Thing map[KeyReference[string]]ValueReference[string] Thing *orderedmap.Map[KeyReference[string], ValueReference[string]]
} }
yml := `thing: yml := `thing:
@@ -256,7 +242,7 @@ func TestSetField_MapHelper(t *testing.T) {
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Len(t, ins.Thing, 3) assert.Equal(t, 3, orderedmap.Len(ins.Thing))
} }
func TestSetField_ArrayHelper(t *testing.T) { func TestSetField_ArrayHelper(t *testing.T) {
@@ -281,7 +267,7 @@ func TestSetField_ArrayHelper(t *testing.T) {
func TestSetField_Enum_Helper(t *testing.T) { func TestSetField_Enum_Helper(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[any]] Thing NodeReference[[]ValueReference[*yaml.Node]]
} }
yml := `thing: yml := `thing:
@@ -324,7 +310,7 @@ func TestSetField_Default_Helper(t *testing.T) {
func TestHandleSlicesOfInts(t *testing.T) { func TestHandleSlicesOfInts(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[any]] Thing NodeReference[[]ValueReference[*yaml.Node]]
} }
yml := `thing: yml := `thing:
@@ -338,13 +324,20 @@ func TestHandleSlicesOfInts(t *testing.T) {
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Equal(t, int64(5), ins.Thing.Value[0].Value)
assert.Equal(t, 1.234, ins.Thing.Value[1].Value) var thing0 int64
_ = ins.Thing.GetValue()[0].Value.Decode(&thing0)
var thing1 float64
_ = ins.Thing.GetValue()[1].Value.Decode(&thing1)
assert.Equal(t, int64(5), thing0)
assert.Equal(t, 1.234, thing1)
} }
func TestHandleSlicesOfBools(t *testing.T) { func TestHandleSlicesOfBools(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[any]] Thing NodeReference[[]ValueReference[*yaml.Node]]
} }
yml := `thing: yml := `thing:
@@ -357,9 +350,16 @@ func TestHandleSlicesOfBools(t *testing.T) {
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
var thing0 bool
_ = ins.Thing.GetValue()[0].Value.Decode(&thing0)
var thing1 bool
_ = ins.Thing.GetValue()[1].Value.Decode(&thing1)
assert.NoError(t, try) assert.NoError(t, try)
assert.Equal(t, true, ins.Thing.Value[0].Value) assert.Equal(t, true, thing0)
assert.Equal(t, false, ins.Thing.Value[1].Value) assert.Equal(t, false, thing1)
} }
func TestSetField_Ignore(t *testing.T) { func TestSetField_Ignore(t *testing.T) {
@@ -387,7 +387,7 @@ func TestSetField_Ignore(t *testing.T) {
func TestBuildModelAsync(t *testing.T) { func TestBuildModelAsync(t *testing.T) {
type internal struct { type internal struct {
Thing KeyReference[map[KeyReference[string]]ValueReference[string]] Thing KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]
} }
yml := `thing: yml := `thing:
@@ -405,28 +405,5 @@ func TestBuildModelAsync(t *testing.T) {
wg.Add(1) wg.Add(1)
BuildModelAsync(rootNode.Content[0], ins, &wg, &errors) BuildModelAsync(rootNode.Content[0], ins, &wg, &errors)
wg.Wait() wg.Wait()
assert.Len(t, ins.Thing.Value, 3) assert.Equal(t, 3, orderedmap.Len(ins.Thing.Value))
}
func TestBuildModelAsync_Error(t *testing.T) {
type internal struct {
Thing []NodeReference[any]
}
yml := `thing:
- 999
- false`
ins := new(internal)
var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr)
var wg sync.WaitGroup
var errors []error
wg.Add(1)
BuildModelAsync(rootNode.Content[0], ins, &wg, &errors)
wg.Wait()
assert.Len(t, errors, 1)
assert.Len(t, ins.Thing, 0)
} }

View File

@@ -3,6 +3,11 @@
package low package low
import (
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
type SharedParameters interface { type SharedParameters interface {
HasDescription HasDescription
Hash() [32]byte Hash() [32]byte
@@ -30,7 +35,7 @@ type SwaggerParameter interface {
GetType() *NodeReference[string] GetType() *NodeReference[string]
GetFormat() *NodeReference[string] GetFormat() *NodeReference[string]
GetCollectionFormat() *NodeReference[string] GetCollectionFormat() *NodeReference[string]
GetDefault() *NodeReference[any] GetDefault() *NodeReference[*yaml.Node]
GetMaximum() *NodeReference[int] GetMaximum() *NodeReference[int]
GetExclusiveMaximum() *NodeReference[bool] GetExclusiveMaximum() *NodeReference[bool]
GetMinimum() *NodeReference[int] GetMinimum() *NodeReference[int]
@@ -41,7 +46,7 @@ type SwaggerParameter interface {
GetMaxItems() *NodeReference[int] GetMaxItems() *NodeReference[int]
GetMinItems() *NodeReference[int] GetMinItems() *NodeReference[int]
GetUniqueItems() *NodeReference[bool] GetUniqueItems() *NodeReference[bool]
GetEnum() *NodeReference[[]ValueReference[any]] GetEnum() *NodeReference[[]ValueReference[*yaml.Node]]
GetMultipleOf() *NodeReference[int] GetMultipleOf() *NodeReference[int]
} }
@@ -51,7 +56,7 @@ type SwaggerHeader interface {
GetType() *NodeReference[string] GetType() *NodeReference[string]
GetFormat() *NodeReference[string] GetFormat() *NodeReference[string]
GetCollectionFormat() *NodeReference[string] GetCollectionFormat() *NodeReference[string]
GetDefault() *NodeReference[any] GetDefault() *NodeReference[*yaml.Node]
GetMaximum() *NodeReference[int] GetMaximum() *NodeReference[int]
GetExclusiveMaximum() *NodeReference[bool] GetExclusiveMaximum() *NodeReference[bool]
GetMinimum() *NodeReference[int] GetMinimum() *NodeReference[int]
@@ -62,7 +67,7 @@ type SwaggerHeader interface {
GetMaxItems() *NodeReference[int] GetMaxItems() *NodeReference[int]
GetMinItems() *NodeReference[int] GetMinItems() *NodeReference[int]
GetUniqueItems() *NodeReference[bool] GetUniqueItems() *NodeReference[bool]
GetEnum() *NodeReference[[]ValueReference[any]] GetEnum() *NodeReference[[]ValueReference[*yaml.Node]]
GetMultipleOf() *NodeReference[int] GetMultipleOf() *NodeReference[int]
GetItems() *NodeReference[any] // requires cast. GetItems() *NodeReference[any] // requires cast.
} }
@@ -74,7 +79,7 @@ type OpenAPIHeader interface {
GetStyle() *NodeReference[string] GetStyle() *NodeReference[string]
GetAllowReserved() *NodeReference[bool] GetAllowReserved() *NodeReference[bool]
GetExplode() *NodeReference[bool] GetExplode() *NodeReference[bool]
GetExample() *NodeReference[any] GetExample() *NodeReference[*yaml.Node]
GetRequired() *NodeReference[bool] GetRequired() *NodeReference[bool]
GetAllowEmptyValue() *NodeReference[bool] GetAllowEmptyValue() *NodeReference[bool]
GetSchema() *NodeReference[any] // requires cast. GetSchema() *NodeReference[any] // requires cast.
@@ -88,13 +93,12 @@ type OpenAPIParameter interface {
GetStyle() *NodeReference[string] GetStyle() *NodeReference[string]
GetAllowReserved() *NodeReference[bool] GetAllowReserved() *NodeReference[bool]
GetExplode() *NodeReference[bool] GetExplode() *NodeReference[bool]
GetExample() *NodeReference[any] GetExample() *NodeReference[*yaml.Node]
GetExamples() *NodeReference[any] // requires cast. GetExamples() *NodeReference[any] // requires cast.
GetContent() *NodeReference[any] // requires cast. GetContent() *NodeReference[any] // requires cast.
} }
//TODO: this needs to be fixed, move returns to pointers. // TODO: this needs to be fixed, move returns to pointers.
type SharedOperations interface { type SharedOperations interface {
GetOperationId() NodeReference[string] GetOperationId() NodeReference[string]
GetExternalDocs() NodeReference[any] GetExternalDocs() NodeReference[any]
@@ -102,7 +106,7 @@ type SharedOperations interface {
GetTags() NodeReference[[]ValueReference[string]] GetTags() NodeReference[[]ValueReference[string]]
GetSummary() NodeReference[string] GetSummary() NodeReference[string]
GetDeprecated() NodeReference[bool] GetDeprecated() NodeReference[bool]
GetExtensions() map[KeyReference[string]]ValueReference[any] GetExtensions() *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]
GetResponses() NodeReference[any] // requires cast. GetResponses() NodeReference[any] // requires cast.
GetParameters() NodeReference[any] // requires cast. GetParameters() NodeReference[any] // requires cast.
GetSecurity() NodeReference[any] // requires cast. GetSecurity() NodeReference[any] // requires cast.
@@ -117,6 +121,6 @@ type SwaggerOperations interface {
type OpenAPIOperations interface { type OpenAPIOperations interface {
SharedOperations SharedOperations
GetCallbacks() NodeReference[map[KeyReference[string]]ValueReference[any]] // requires cast GetCallbacks() NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[any]]] // requires cast
GetServers() NodeReference[any] // requires cast. GetServers() NodeReference[any] // requires cast.
} }

View File

@@ -3,7 +3,9 @@ package low
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -13,25 +15,35 @@ const (
) )
type Reference struct { type Reference struct {
Reference string `json:"-" yaml:"-"` refNode *yaml.Node
reference string
} }
func (r *Reference) GetReference() string { func (r Reference) GetReference() string {
return r.Reference return r.reference
} }
func (r *Reference) IsReference() bool { func (r Reference) IsReference() bool {
return r.Reference != "" return r.reference != ""
} }
func (r *Reference) SetReference(ref string) { func (r Reference) GetReferenceNode() *yaml.Node {
r.Reference = ref return r.refNode
}
func (r *Reference) SetReference(ref string, node *yaml.Node) {
r.reference = ref
r.refNode = node
} }
type IsReferenced interface { type IsReferenced interface {
IsReference() bool IsReference() bool
GetReference() string GetReference() string
SetReference(string) GetReferenceNode() *yaml.Node
}
type SetReferencer interface {
SetReference(ref string, node *yaml.Node)
} }
// Buildable is an interface for any struct that can be 'built out'. This means that a struct can accept // Buildable is an interface for any struct that can be 'built out'. This means that a struct can accept
@@ -63,16 +75,14 @@ type Hashable interface {
// HasExtensions is implemented by any object that exposes extensions // HasExtensions is implemented by any object that exposes extensions
type HasExtensions[T any] interface { type HasExtensions[T any] interface {
// GetExtensions returns generic low level extensions // GetExtensions returns generic low level extensions
GetExtensions() map[KeyReference[string]]ValueReference[any] GetExtensions() *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]
} }
// HasExtensionsUntyped is implemented by any object that exposes extensions // HasExtensionsUntyped is implemented by any object that exposes extensions
type HasExtensionsUntyped interface { type HasExtensionsUntyped interface {
// GetExtensions returns generic low level extensions // GetExtensions returns generic low level extensions
GetExtensions() map[KeyReference[string]]ValueReference[any] GetExtensions() *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]
} }
// HasValue is implemented by NodeReference and ValueReference to return the yaml.Node backing the value. // HasValue is implemented by NodeReference and ValueReference to return the yaml.Node backing the value.
@@ -98,6 +108,7 @@ type HasKeyNode interface {
// a key yaml.Node that points to the key node that contains the value node, and the value node that contains // a key yaml.Node that points to the key node that contains the value node, and the value node that contains
// the actual value. // the actual value.
type NodeReference[T any] struct { type NodeReference[T any] struct {
Reference
// The value being referenced // The value being referenced
Value T Value T
@@ -108,19 +119,14 @@ type NodeReference[T any] struct {
// The yaml.Node that is the key, that contains the value. // The yaml.Node that is the key, that contains the value.
KeyNode *yaml.Node KeyNode *yaml.Node
// Is this value actually a reference in the original tree?
ReferenceNode bool
// If HasReference is true, then Reference contains the original $ref value.
Reference string
Context context.Context Context context.Context
} }
var _ HasValueNodeUntyped = &NodeReference[any]{}
// KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the // KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the
// yaml.Node that holds a key to a value. // yaml.Node that holds a key to a value.
type KeyReference[T any] struct { type KeyReference[T any] struct {
// The value being referenced. // The value being referenced.
Value T Value T
@@ -131,18 +137,13 @@ type KeyReference[T any] struct {
// ValueReference is a low-level container for value nodes that hold a Value of type T. A ValueNode is a pointer // ValueReference is a low-level container for value nodes that hold a Value of type T. A ValueNode is a pointer
// to the yaml.Node that holds the value. // to the yaml.Node that holds the value.
type ValueReference[T any] struct { type ValueReference[T any] struct {
Reference
// The value being referenced. // The value being referenced.
Value T Value T
// The yaml.Node that holds the referenced value // The yaml.Node that holds the referenced value
ValueNode *yaml.Node ValueNode *yaml.Node
// Is this value actually a reference in the original tree?
ReferenceNode bool
// If HasReference is true, then Reference contains the original $ref value.
Reference string
} }
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored) // IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
@@ -158,32 +159,6 @@ func (n NodeReference[T]) NodeLineNumber() int {
} }
} }
func (n NodeReference[T]) GetReference() string {
return n.Reference
}
func (n NodeReference[T]) SetReference(ref string) {
n.Reference = ref
}
// IsReference will return true if the key node contains a $ref key.
func (n NodeReference[T]) IsReference() bool {
if n.ReferenceNode {
return true
}
if n.KeyNode != nil {
for k := range n.KeyNode.Content {
if k%2 == 0 {
if n.KeyNode.Content[k].Value == "$ref" {
n.ReferenceNode = true
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. // 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 { func (n NodeReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column) return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
@@ -251,36 +226,19 @@ func (n ValueReference[T]) GetValueUntyped() any {
return n.Value return n.Value
} }
func (n ValueReference[T]) GetReference() string {
return n.Reference
}
func (n ValueReference[T]) SetReference(ref string) {
n.Reference = ref
}
// IsReference will return true if the key node contains a $ref
func (n ValueReference[T]) IsReference() bool {
if n.Reference != "" {
return true
}
return false
}
func (n ValueReference[T]) MarshalYAML() (interface{}, error) { func (n ValueReference[T]) MarshalYAML() (interface{}, error) {
if n.IsReference() { if n.IsReference() {
nodes := make([]*yaml.Node, 2) return n.GetReferenceNode(), nil
nodes[0] = utils.CreateStringNode("$ref")
nodes[1] = utils.CreateStringNode(n.Reference)
m := utils.CreateEmptyMapNode()
m.Content = nodes
return m, nil
} }
var h yaml.Node var h yaml.Node
e := n.ValueNode.Decode(&h) e := n.ValueNode.Decode(&h)
return h, e return h, e
} }
func (n KeyReference[T]) MarshalYAML() (interface{}, error) {
return n.KeyNode, nil
}
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored) // IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n KeyReference[T]) IsEmpty() bool { func (n KeyReference[T]) IsEmpty() bool {
return n.KeyNode == nil return n.KeyNode == nil

View File

@@ -6,10 +6,11 @@ package low
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils"
"strings" "strings"
"testing" "testing"
"github.com/pb33f/libopenapi/utils"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -99,7 +100,6 @@ func TestKeyReference_GenerateMapKey(t *testing.T) {
} }
func TestIsCircular_LookupFromJourney(t *testing.T) { func TestIsCircular_LookupFromJourney(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
Something: Something:
@@ -136,7 +136,6 @@ func TestIsCircular_LookupFromJourney(t *testing.T) {
} }
func TestIsCircular_LookupFromJourney_Optional(t *testing.T) { func TestIsCircular_LookupFromJourney_Optional(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
Something: Something:
@@ -237,7 +236,6 @@ func TestIsCircular_LookupFromLoopPoint_Optional(t *testing.T) {
} }
func TestIsCircular_FromRefLookup(t *testing.T) { func TestIsCircular_FromRefLookup(t *testing.T) {
yml := `components: yml := `components:
schemas: schemas:
NotCircle: NotCircle:
@@ -558,21 +556,17 @@ func TestGetCircularReferenceResult_NothingFound(t *testing.T) {
func TestHashToString(t *testing.T) { func TestHashToString(t *testing.T) {
assert.Equal(t, "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5", assert.Equal(t, "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5",
HashToString(sha256.Sum256([]byte("12345")))) HashToString(sha256.Sum256([]byte("12345"))))
} }
func TestReference_IsReference(t *testing.T) { func TestReference_IsReference(t *testing.T) {
ref := Reference{ ref := Reference{}
Reference: "#/components/schemas/SomeSchema", ref.SetReference("#/components/schemas/SomeSchema", nil)
}
assert.True(t, ref.IsReference()) assert.True(t, ref.IsReference())
} }
func TestNodeReference_NodeLineNumber(t *testing.T) { func TestNodeReference_NodeLineNumber(t *testing.T) {
n := utils.CreateStringNode("pizza") n := utils.CreateStringNode("pizza")
nr := NodeReference[string]{ nr := &NodeReference[string]{
Value: "pizza", Value: "pizza",
ValueNode: n, ValueNode: n,
} }
@@ -582,51 +576,36 @@ func TestNodeReference_NodeLineNumber(t *testing.T) {
} }
func TestNodeReference_NodeLineNumberEmpty(t *testing.T) { func TestNodeReference_NodeLineNumberEmpty(t *testing.T) {
nr := &NodeReference[string]{
nr := NodeReference[string]{
Value: "pizza", Value: "pizza",
} }
assert.Equal(t, 0, nr.NodeLineNumber()) assert.Equal(t, 0, nr.NodeLineNumber())
} }
func TestNodeReference_GetReference(t *testing.T) { func TestNodeReference_GetReference(t *testing.T) {
nr := &NodeReference[string]{}
nr := NodeReference[string]{ nr.SetReference("#/happy/sunday", nil)
Reference: "#/happy/sunday",
}
assert.Equal(t, "#/happy/sunday", nr.GetReference()) assert.Equal(t, "#/happy/sunday", nr.GetReference())
} }
func TestNodeReference_SetReference(t *testing.T) { func TestNodeReference_SetReference(t *testing.T) {
nr := &NodeReference[string]{}
nr := NodeReference[string]{} nr.SetReference("#/happy/sunday", nil)
nr.SetReference("#/happy/sunday")
}
func TestNodeReference_IsReference(t *testing.T) {
nr := NodeReference[string]{
ReferenceNode: true,
}
assert.True(t, nr.IsReference())
} }
func TestNodeReference_GetKeyNode(t *testing.T) { func TestNodeReference_GetKeyNode(t *testing.T) {
nr := &NodeReference[string]{
nr := NodeReference[string]{
KeyNode: utils.CreateStringNode("pizza"), KeyNode: utils.CreateStringNode("pizza"),
} }
assert.Equal(t, "pizza", nr.GetKeyNode().Value) assert.Equal(t, "pizza", nr.GetKeyNode().Value)
} }
func TestNodeReference_GetValueUntyped(t *testing.T) { func TestNodeReference_GetValueUntyped(t *testing.T) {
type anything struct { type anything struct {
thing string thing string
} }
nr := NodeReference[any]{ nr := &NodeReference[any]{
Value: anything{thing: "ding"}, Value: anything{thing: "ding"},
} }
@@ -634,7 +613,6 @@ func TestNodeReference_GetValueUntyped(t *testing.T) {
} }
func TestValueReference_NodeLineNumber(t *testing.T) { func TestValueReference_NodeLineNumber(t *testing.T) {
n := utils.CreateStringNode("pizza") n := utils.CreateStringNode("pizza")
nr := ValueReference[string]{ nr := ValueReference[string]{
Value: "pizza", Value: "pizza",
@@ -646,7 +624,6 @@ func TestValueReference_NodeLineNumber(t *testing.T) {
} }
func TestValueReference_NodeLineNumber_Nil(t *testing.T) { func TestValueReference_NodeLineNumber_Nil(t *testing.T) {
nr := ValueReference[string]{ nr := ValueReference[string]{
Value: "pizza", Value: "pizza",
} }
@@ -655,21 +632,12 @@ func TestValueReference_NodeLineNumber_Nil(t *testing.T) {
} }
func TestValueReference_GetReference(t *testing.T) { func TestValueReference_GetReference(t *testing.T) {
nr := ValueReference[string]{}
nr := ValueReference[string]{ nr.SetReference("#/happy/sunday", nil)
Reference: "#/happy/sunday",
}
assert.Equal(t, "#/happy/sunday", nr.GetReference()) assert.Equal(t, "#/happy/sunday", nr.GetReference())
} }
func TestValueReference_SetReference(t *testing.T) {
nr := ValueReference[string]{}
nr.SetReference("#/happy/sunday")
}
func TestValueReference_GetValueUntyped(t *testing.T) { func TestValueReference_GetValueUntyped(t *testing.T) {
type anything struct { type anything struct {
thing string thing string
} }
@@ -681,28 +649,15 @@ func TestValueReference_GetValueUntyped(t *testing.T) {
assert.Equal(t, "{ding}", fmt.Sprint(nr.GetValueUntyped())) assert.Equal(t, "{ding}", fmt.Sprint(nr.GetValueUntyped()))
} }
func TestValueReference_IsReference(t *testing.T) {
nr := NodeReference[string]{
ReferenceNode: true,
}
assert.True(t, nr.IsReference())
}
func TestValueReference_MarshalYAML_Ref(t *testing.T) { func TestValueReference_MarshalYAML_Ref(t *testing.T) {
nr := ValueReference[string]{}
nr := ValueReference[string]{ nr.SetReference("#/burgers/beer", nil)
ReferenceNode: true,
Reference: "#/burgers/beer",
}
data, _ := yaml.Marshal(nr) data, _ := yaml.Marshal(nr)
assert.Equal(t, `$ref: '#/burgers/beer'`, strings.TrimSpace(string(data))) assert.Equal(t, `$ref: '#/burgers/beer'`, strings.TrimSpace(string(data)))
} }
func TestValueReference_MarshalYAML(t *testing.T) { func TestValueReference_MarshalYAML(t *testing.T) {
v := map[string]interface{}{ v := map[string]interface{}{
"beer": "burger", "beer": "burger",
"wine": "cheese", "wine": "cheese",
@@ -725,7 +680,6 @@ wine: cheese`
} }
func TestKeyReference_GetValueUntyped(t *testing.T) { func TestKeyReference_GetValueUntyped(t *testing.T) {
type anything struct { type anything struct {
thing string thing string
} }

View File

@@ -6,9 +6,10 @@ package v2
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"sort"
"strings" "strings"
"sync"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -23,7 +24,7 @@ import (
// referenced to the ones defined here. It does not define global operation parameters // referenced to the ones defined here. It does not define global operation parameters
// - https://swagger.io/specification/v2/#parametersDefinitionsObject // - https://swagger.io/specification/v2/#parametersDefinitionsObject
type ParameterDefinitions struct { type ParameterDefinitions struct {
Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*Parameter]] Definitions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*Parameter]]
} }
// ResponsesDefinitions is a low-level representation of a Swagger / OpenAPI 2 Responses Definitions object. // ResponsesDefinitions is a low-level representation of a Swagger / OpenAPI 2 Responses Definitions object.
@@ -32,7 +33,7 @@ type ParameterDefinitions struct {
// referenced to the ones defined here. It does not define global operation responses // referenced to the ones defined here. It does not define global operation responses
// - https://swagger.io/specification/v2/#responsesDefinitionsObject // - https://swagger.io/specification/v2/#responsesDefinitionsObject
type ResponsesDefinitions struct { type ResponsesDefinitions struct {
Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]] Definitions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]]
} }
// SecurityDefinitions is a low-level representation of a Swagger / OpenAPI 2 Security Definitions object. // SecurityDefinitions is a low-level representation of a Swagger / OpenAPI 2 Security Definitions object.
@@ -41,7 +42,7 @@ type ResponsesDefinitions struct {
// schemes on the operations and only serves to provide the relevant details for each scheme // schemes on the operations and only serves to provide the relevant details for each scheme
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityDefinitions struct { type SecurityDefinitions struct {
Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*SecurityScheme]] Definitions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*SecurityScheme]]
} }
// Definitions is a low-level representation of a Swagger / OpenAPI 2 Definitions object // Definitions is a low-level representation of a Swagger / OpenAPI 2 Definitions object
@@ -50,7 +51,7 @@ type SecurityDefinitions struct {
// arrays or models. // arrays or models.
// - https://swagger.io/specification/v2/#definitionsObject // - https://swagger.io/specification/v2/#definitionsObject
type Definitions struct { type Definitions struct {
Schemas orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]] Schemas *orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]
} }
// FindSchema will attempt to locate a base.SchemaProxy instance using a name. // FindSchema will attempt to locate a base.SchemaProxy instance using a name.
@@ -77,46 +78,79 @@ func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.Va
func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error { func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
// TODO: Refactor with orderedmap.TranslatePipeline. type buildInput struct {
errorChan := make(chan error) label *yaml.Node
resultChan := make(chan definitionResult[*base.SchemaProxy]) value *yaml.Node
var defLabel *yaml.Node }
totalDefinitions := 0 results := orderedmap.New[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]()
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, in := make(chan buildInput)
r chan definitionResult[*base.SchemaProxy], e chan error) { out := make(chan definitionResult[*base.SchemaProxy])
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(2) // input and output goroutines.
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](ctx, label, value, idx) // TranslatePipeline input.
if err != nil { go func() {
e <- err defer func() {
} close(in)
r <- definitionResult[*base.SchemaProxy]{k: label, v: low.ValueReference[*base.SchemaProxy]{ wg.Done()
Value: obj, ValueNode: value, Reference: rv, }()
}} var label *yaml.Node
} for i, value := range root.Content {
for i := range root.Content {
if i%2 == 0 { if i%2 == 0 {
defLabel = root.Content[i] label = value
continue continue
} }
totalDefinitions++
go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) select {
case in <- buildInput{
label: label,
value: value,
}:
case <-done:
return
}
}
}()
// TranslatePipeline output.
go func() {
for {
result, ok := <-out
if !ok {
break
} }
completedDefs := 0
results := orderedmap.New[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]()
for completedDefs < totalDefinitions {
select {
case err := <-errorChan:
return err
case sch := <-resultChan:
completedDefs++
key := low.KeyReference[string]{ key := low.KeyReference[string]{
Value: sch.k.Value, Value: result.k.Value,
KeyNode: sch.k, KeyNode: result.k,
} }
results.Set(key, sch.v) results.Set(key, result.v)
} }
close(done)
wg.Done()
}()
translateFunc := func(value buildInput) (definitionResult[*base.SchemaProxy], error) {
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](ctx, value.label, value.value, idx)
if err != nil {
return definitionResult[*base.SchemaProxy]{}, err
} }
v := low.ValueReference[*base.SchemaProxy]{
Value: obj, ValueNode: value.value,
}
v.SetReference(rv, value.value)
return definitionResult[*base.SchemaProxy]{k: value.label, v: v}, nil
}
err := datamodel.TranslatePipeline[buildInput, definitionResult[*base.SchemaProxy]](in, out, translateFunc)
wg.Wait()
if err != nil {
return err
}
d.Schemas = results d.Schemas = results
return nil return nil
} }
@@ -124,15 +158,8 @@ func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.
// Hash will return a consistent SHA256 Hash of the Definitions object // Hash will return a consistent SHA256 Hash of the Definitions object
func (d *Definitions) Hash() [32]byte { func (d *Definitions) Hash() [32]byte {
var f []string var f []string
keys := make([]string, orderedmap.Len(d.Schemas)) for pair := orderedmap.First(orderedmap.SortAlpha(d.Schemas)); pair != nil; pair = pair.Next() {
z := 0 f = append(f, low.GenerateHashString(d.FindSchema(pair.Key().Value).Value))
for pair := orderedmap.First(d.Schemas); pair != nil; pair = pair.Next() {
keys[z] = pair.Key().Value
z++
}
sort.Strings(keys)
for k := range keys {
f = append(f, low.GenerateHashString(d.FindSchema(keys[k]).Value))
} }
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }
@@ -143,15 +170,21 @@ func (pd *ParameterDefinitions) Build(ctx context.Context, _, root *yaml.Node, i
resultChan := make(chan definitionResult[*Parameter]) resultChan := make(chan definitionResult[*Parameter])
var defLabel *yaml.Node var defLabel *yaml.Node
totalDefinitions := 0 totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, buildFunc := func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Parameter], e chan error) { r chan definitionResult[*Parameter], e chan error,
) {
obj, err, _, rv := low.ExtractObjectRaw[*Parameter](ctx, label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*Parameter](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }
r <- definitionResult[*Parameter]{k: label, v: low.ValueReference[*Parameter]{Value: obj,
ValueNode: value, Reference: rv}} v := low.ValueReference[*Parameter]{
Value: obj,
ValueNode: value,
}
v.SetReference(rv, value)
r <- definitionResult[*Parameter]{k: label, v: v}
} }
for i := range root.Content { for i := range root.Content {
if i%2 == 0 { if i%2 == 0 {
@@ -193,15 +226,21 @@ func (r *ResponsesDefinitions) Build(ctx context.Context, _, root *yaml.Node, id
resultChan := make(chan definitionResult[*Response]) resultChan := make(chan definitionResult[*Response])
var defLabel *yaml.Node var defLabel *yaml.Node
totalDefinitions := 0 totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, buildFunc := func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Response], e chan error) { r chan definitionResult[*Response], e chan error,
) {
obj, err, _, rv := low.ExtractObjectRaw[*Response](ctx, label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*Response](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }
r <- definitionResult[*Response]{k: label, v: low.ValueReference[*Response]{Value: obj,
ValueNode: value, Reference: rv}} v := low.ValueReference[*Response]{
Value: obj,
ValueNode: value,
}
v.SetReference(rv, value)
r <- definitionResult[*Response]{k: label, v: v}
} }
for i := range root.Content { for i := range root.Content {
if i%2 == 0 { if i%2 == 0 {
@@ -238,16 +277,20 @@ func (s *SecurityDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx
var defLabel *yaml.Node var defLabel *yaml.Node
totalDefinitions := 0 totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, buildFunc := func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*SecurityScheme], e chan error) { r chan definitionResult[*SecurityScheme], e chan error,
) {
obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](ctx, label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }
r <- definitionResult[*SecurityScheme]{k: label, v: low.ValueReference[*SecurityScheme]{
Value: obj, ValueNode: value, Reference: rv, v := low.ValueReference[*SecurityScheme]{
}} Value: obj, ValueNode: value,
}
v.SetReference(rv, value)
r <- definitionResult[*SecurityScheme]{k: label, v: v}
} }
for i := range root.Content { for i := range root.Content {

View File

@@ -6,8 +6,6 @@ package v2
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt"
"sort"
"strings" "strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -21,12 +19,12 @@ import (
// Allows sharing examples for operation responses // Allows sharing examples for operation responses
// - https://swagger.io/specification/v2/#exampleObject // - https://swagger.io/specification/v2/#exampleObject
type Examples struct { type Examples struct {
Values orderedmap.Map[low.KeyReference[string], low.ValueReference[any]] Values *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
// FindExample attempts to locate an example value, using a key label. // FindExample attempts to locate an example value, using a key label.
func (e *Examples) FindExample(name string) *low.ValueReference[any] { func (e *Examples) FindExample(name string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap[any](name, e.Values) return low.FindItemInOrderedMap(name, e.Values)
} }
// Build will extract all examples and will attempt to unmarshal content into a map or slice based on type. // Build will extract all examples and will attempt to unmarshal content into a map or slice based on type.
@@ -34,54 +32,21 @@ func (e *Examples) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
var keyNode, currNode *yaml.Node var keyNode, currNode *yaml.Node
var err error e.Values = orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
e.Values = orderedmap.New[low.KeyReference[string], low.ValueReference[any]]()
for i := range root.Content { for i := range root.Content {
if i%2 == 0 { if i%2 == 0 {
keyNode = root.Content[i] keyNode = root.Content[i]
continue continue
} }
currNode = root.Content[i] currNode = root.Content[i]
var n map[string]interface{}
err = currNode.Decode(&n)
if err != nil {
var k []interface{}
err = currNode.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = currNode.Decode(&j)
e.Values.Set( e.Values.Set(
low.KeyReference[string]{ low.KeyReference[string]{
Value: keyNode.Value, Value: keyNode.Value,
KeyNode: keyNode, KeyNode: keyNode,
}, },
low.ValueReference[any]{ low.ValueReference[*yaml.Node]{
Value: j, Value: currNode,
ValueNode: currNode,
},
)
continue
}
e.Values.Set(
low.KeyReference[string]{
Value: keyNode.Value,
KeyNode: keyNode,
},
low.ValueReference[any]{
Value: k,
ValueNode: currNode,
},
)
continue
}
e.Values.Set(
low.KeyReference[string]{
Value: keyNode.Value,
KeyNode: keyNode,
},
low.ValueReference[any]{
Value: n,
ValueNode: currNode, ValueNode: currNode,
}, },
) )
@@ -92,15 +57,8 @@ func (e *Examples) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
// Hash will return a consistent SHA256 Hash of the Examples object // Hash will return a consistent SHA256 Hash of the Examples object
func (e *Examples) Hash() [32]byte { func (e *Examples) Hash() [32]byte {
var f []string var f []string
keys := make([]string, orderedmap.Len(e.Values)) for pair := orderedmap.First(orderedmap.SortAlpha(e.Values)); pair != nil; pair = pair.Next() {
z := 0 f = append(f, low.GenerateHashString(pair.Value().Value))
for pair := orderedmap.First(e.Values); pair != nil; pair = pair.Next() {
keys[z] = pair.Key().Value
z++
}
sort.Strings(keys)
for k := range keys {
f = append(f, fmt.Sprintf("%v", e.FindExample(keys[k]).Value))
} }
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -7,12 +7,14 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
) )
// Header Represents a low-level Swagger / OpenAPI 2 Header object. // Header Represents a low-level Swagger / OpenAPI 2 Header object.
@@ -25,7 +27,7 @@ type Header struct {
Description low.NodeReference[string] Description low.NodeReference[string]
Items low.NodeReference[*Items] Items low.NodeReference[*Items]
CollectionFormat low.NodeReference[string] CollectionFormat low.NodeReference[string]
Default low.NodeReference[any] Default low.NodeReference[*yaml.Node]
Maximum low.NodeReference[int] Maximum low.NodeReference[int]
ExclusiveMaximum low.NodeReference[bool] ExclusiveMaximum low.NodeReference[bool]
Minimum low.NodeReference[int] Minimum low.NodeReference[int]
@@ -36,18 +38,18 @@ type Header struct {
MaxItems low.NodeReference[int] MaxItems low.NodeReference[int]
MinItems low.NodeReference[int] MinItems low.NodeReference[int]
UniqueItems low.NodeReference[bool] UniqueItems low.NodeReference[bool]
Enum low.NodeReference[[]low.ValueReference[any]] Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
MultipleOf low.NodeReference[int] MultipleOf low.NodeReference[int]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
// FindExtension will attempt to locate an extension value using a name lookup. // FindExtension will attempt to locate an extension value using a name lookup.
func (h *Header) FindExtension(ext string) *low.ValueReference[any] { func (h *Header) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, h.Extensions) return low.FindItemInOrderedMap(ext, h.Extensions)
} }
// GetExtensions returns all Header extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Header extensions and satisfies the low.HasExtensions interface.
func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (h *Header) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return h.Extensions return h.Extensions
} }
@@ -64,37 +66,14 @@ func (h *Header) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecI
_, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content)
if vn != nil { if vn != nil {
var n map[string]interface{} h.Default = low.NodeReference[*yaml.Node]{
err = vn.Decode(&n) Value: vn,
if err != nil {
// if not a map, then try an array
var k []interface{}
err = vn.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = vn.Decode(&j)
h.Default = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
h.Default = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
h.Default = low.NodeReference[any]{
Value: n,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
return nil return nil
} }
return nil return nil
} }
@@ -113,8 +92,8 @@ func (h *Header) Hash() [32]byte {
if h.CollectionFormat.Value != "" { if h.CollectionFormat.Value != "" {
f = append(f, h.CollectionFormat.Value) f = append(f, h.CollectionFormat.Value)
} }
if h.Default.Value != "" { if h.Default.Value != nil && !h.Default.Value.IsZero() {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(h.Default.Value))))) f = append(f, low.GenerateHashString(h.Default.Value))
} }
f = append(f, fmt.Sprint(h.Maximum.Value)) f = append(f, fmt.Sprint(h.Maximum.Value))
f = append(f, fmt.Sprint(h.Minimum.Value)) f = append(f, fmt.Sprint(h.Minimum.Value))
@@ -129,24 +108,17 @@ func (h *Header) Hash() [32]byte {
if h.Pattern.Value != "" { if h.Pattern.Value != "" {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(h.Pattern.Value))))) f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(h.Pattern.Value)))))
} }
f = append(f, low.HashExtensions(h.Extensions)...)
keys := make([]string, len(h.Extensions)) keys := make([]string, len(h.Enum.Value))
z := 0 z := 0
for k := range h.Extensions { for k := range h.Enum.Value {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(h.Extensions[k].Value)))) keys[z] = low.ValueToString(h.Enum.Value[k].Value)
z++ z++
} }
sort.Strings(keys) sort.Strings(keys)
f = append(f, 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 { if h.Items.Value != nil {
f = append(f, low.GenerateHashString(h.Items.Value)) f = append(f, low.GenerateHashString(h.Items.Value))
} }
@@ -158,12 +130,15 @@ func (h *Header) Hash() [32]byte {
func (h *Header) GetType() *low.NodeReference[string] { func (h *Header) GetType() *low.NodeReference[string] {
return &h.Type return &h.Type
} }
func (h *Header) GetDescription() *low.NodeReference[string] { func (h *Header) GetDescription() *low.NodeReference[string] {
return &h.Description return &h.Description
} }
func (h *Header) GetFormat() *low.NodeReference[string] { func (h *Header) GetFormat() *low.NodeReference[string] {
return &h.Format return &h.Format
} }
func (h *Header) GetItems() *low.NodeReference[any] { func (h *Header) GetItems() *low.NodeReference[any] {
i := low.NodeReference[any]{ i := low.NodeReference[any]{
KeyNode: h.Items.KeyNode, KeyNode: h.Items.KeyNode,
@@ -172,45 +147,59 @@ func (h *Header) GetItems() *low.NodeReference[any] {
} }
return &i return &i
} }
func (h *Header) GetCollectionFormat() *low.NodeReference[string] { func (h *Header) GetCollectionFormat() *low.NodeReference[string] {
return &h.CollectionFormat return &h.CollectionFormat
} }
func (h *Header) GetDefault() *low.NodeReference[any] {
func (h *Header) GetDefault() *low.NodeReference[*yaml.Node] {
return &h.Default return &h.Default
} }
func (h *Header) GetMaximum() *low.NodeReference[int] { func (h *Header) GetMaximum() *low.NodeReference[int] {
return &h.Maximum return &h.Maximum
} }
func (h *Header) GetExclusiveMaximum() *low.NodeReference[bool] { func (h *Header) GetExclusiveMaximum() *low.NodeReference[bool] {
return &h.ExclusiveMaximum return &h.ExclusiveMaximum
} }
func (h *Header) GetMinimum() *low.NodeReference[int] { func (h *Header) GetMinimum() *low.NodeReference[int] {
return &h.Minimum return &h.Minimum
} }
func (h *Header) GetExclusiveMinimum() *low.NodeReference[bool] { func (h *Header) GetExclusiveMinimum() *low.NodeReference[bool] {
return &h.ExclusiveMinimum return &h.ExclusiveMinimum
} }
func (h *Header) GetMaxLength() *low.NodeReference[int] { func (h *Header) GetMaxLength() *low.NodeReference[int] {
return &h.MaxLength return &h.MaxLength
} }
func (h *Header) GetMinLength() *low.NodeReference[int] { func (h *Header) GetMinLength() *low.NodeReference[int] {
return &h.MinLength return &h.MinLength
} }
func (h *Header) GetPattern() *low.NodeReference[string] { func (h *Header) GetPattern() *low.NodeReference[string] {
return &h.Pattern return &h.Pattern
} }
func (h *Header) GetMaxItems() *low.NodeReference[int] { func (h *Header) GetMaxItems() *low.NodeReference[int] {
return &h.MaxItems return &h.MaxItems
} }
func (h *Header) GetMinItems() *low.NodeReference[int] { func (h *Header) GetMinItems() *low.NodeReference[int] {
return &h.MinItems return &h.MinItems
} }
func (h *Header) GetUniqueItems() *low.NodeReference[bool] { func (h *Header) GetUniqueItems() *low.NodeReference[bool] {
return &h.UniqueItems return &h.UniqueItems
} }
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[*yaml.Node]] {
return &h.Enum return &h.Enum
} }
func (h *Header) GetMultipleOf() *low.NodeReference[int] { func (h *Header) GetMultipleOf() *low.NodeReference[int] {
return &h.MultipleOf return &h.MultipleOf
} }

View File

@@ -5,15 +5,16 @@ package v2
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestHeader_Build(t *testing.T) { func TestHeader_Build(t *testing.T) {
yml := `items: yml := `items:
$ref: break` $ref: break`
@@ -28,11 +29,9 @@ func TestHeader_Build(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestHeader_DefaultAsSlice(t *testing.T) { func TestHeader_DefaultAsSlice(t *testing.T) {
yml := `x-ext: thing yml := `x-ext: thing
default: default:
- why - why
@@ -48,12 +47,15 @@ default:
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NotNil(t, n.Default.Value) assert.NotNil(t, n.Default.Value)
assert.Len(t, n.Default.Value, 3)
assert.Len(t, n.GetExtensions(), 1) var def []string
_ = n.Default.GetValue().Decode(&def)
assert.Len(t, def, 3)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }
func TestHeader_DefaultAsObject(t *testing.T) { func TestHeader_DefaultAsObject(t *testing.T) {
yml := `default: yml := `default:
lets: lets:
create: create:
@@ -72,7 +74,6 @@ func TestHeader_DefaultAsObject(t *testing.T) {
} }
func TestHeader_NoDefault(t *testing.T) { func TestHeader_NoDefault(t *testing.T) {
yml := `minimum: 12` yml := `minimum: 12`
var idxNode yaml.Node var idxNode yaml.Node
@@ -87,7 +88,6 @@ func TestHeader_NoDefault(t *testing.T) {
} }
func TestHeader_Hash_n_Grab(t *testing.T) { func TestHeader_Hash_n_Grab(t *testing.T) {
yml := `description: head yml := `description: head
type: string type: string
format: left format: left
@@ -160,7 +160,11 @@ pattern: wow
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value) assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value)
var def string
_ = n.GetDefault().Value.Decode(&def)
assert.Equal(t, "shut that door!", def)
assert.Equal(t, 10, n.GetMaximum().Value) assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value) assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value) assert.True(t, n.GetExclusiveMinimum().Value)
@@ -174,6 +178,8 @@ pattern: wow
assert.Equal(t, "wow", n.GetPattern().Value) assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value) assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2) assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value)
var xBelly string
_ = n.FindExtension("x-belly").GetValue().Decode(&xBelly)
assert.Equal(t, "large", xBelly)
} }

View File

@@ -7,12 +7,14 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
) )
// Items is a low-level representation of a Swagger / OpenAPI 2 Items object. // Items is a low-level representation of a Swagger / OpenAPI 2 Items object.
@@ -25,7 +27,7 @@ type Items struct {
Format low.NodeReference[string] Format low.NodeReference[string]
CollectionFormat low.NodeReference[string] CollectionFormat low.NodeReference[string]
Items low.NodeReference[*Items] Items low.NodeReference[*Items]
Default low.NodeReference[any] Default low.NodeReference[*yaml.Node]
Maximum low.NodeReference[int] Maximum low.NodeReference[int]
ExclusiveMaximum low.NodeReference[bool] ExclusiveMaximum low.NodeReference[bool]
Minimum low.NodeReference[int] Minimum low.NodeReference[int]
@@ -36,18 +38,18 @@ type Items struct {
MaxItems low.NodeReference[int] MaxItems low.NodeReference[int]
MinItems low.NodeReference[int] MinItems low.NodeReference[int]
UniqueItems low.NodeReference[bool] UniqueItems low.NodeReference[bool]
Enum low.NodeReference[[]low.ValueReference[any]] Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
MultipleOf low.NodeReference[int] MultipleOf low.NodeReference[int]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
// FindExtension will attempt to locate an extension value using a name lookup. // FindExtension will attempt to locate an extension value using a name lookup.
func (i *Items) FindExtension(ext string) *low.ValueReference[any] { func (i *Items) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, i.Extensions) return low.FindItemInOrderedMap(ext, i.Extensions)
} }
// GetExtensions returns all Items extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Items extensions and satisfies the low.HasExtensions interface.
func (i *Items) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (i *Items) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return i.Extensions return i.Extensions
} }
@@ -63,8 +65,8 @@ func (i *Items) Hash() [32]byte {
if i.CollectionFormat.Value != "" { if i.CollectionFormat.Value != "" {
f = append(f, i.CollectionFormat.Value) f = append(f, i.CollectionFormat.Value)
} }
if i.Default.Value != "" { if i.Default.Value != nil && !i.Default.Value.IsZero() {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(i.Default.Value))))) f = append(f, low.GenerateHashString(i.Default.Value))
} }
f = append(f, fmt.Sprint(i.Maximum.Value)) f = append(f, fmt.Sprint(i.Maximum.Value))
f = append(f, fmt.Sprint(i.Minimum.Value)) f = append(f, fmt.Sprint(i.Minimum.Value))
@@ -82,7 +84,7 @@ func (i *Items) Hash() [32]byte {
keys := make([]string, len(i.Enum.Value)) keys := make([]string, len(i.Enum.Value))
z := 0 z := 0
for k := range i.Enum.Value { for k := range i.Enum.Value {
keys[z] = fmt.Sprint(i.Enum.Value[k].Value) keys[z] = low.ValueToString(i.Enum.Value[k].Value)
z++ z++
} }
sort.Strings(keys) sort.Strings(keys)
@@ -91,14 +93,7 @@ func (i *Items) Hash() [32]byte {
if i.Items.Value != nil { if i.Items.Value != nil {
f = append(f, low.GenerateHashString(i.Items.Value)) f = append(f, low.GenerateHashString(i.Items.Value))
} }
keys = make([]string, len(i.Extensions)) f = append(f, low.HashExtensions(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, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }
@@ -115,32 +110,8 @@ func (i *Items) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIn
_, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content)
if vn != nil { if vn != nil {
var n map[string]interface{} i.Default = low.NodeReference[*yaml.Node]{
err := vn.Decode(&n) Value: vn,
if err != nil {
// if not a map, then try an array
var k []interface{}
err = vn.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = vn.Decode(&j)
i.Default = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
i.Default = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
i.Default = low.NodeReference[any]{
Value: n,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
@@ -154,9 +125,11 @@ func (i *Items) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIn
func (i *Items) GetType() *low.NodeReference[string] { func (i *Items) GetType() *low.NodeReference[string] {
return &i.Type return &i.Type
} }
func (i *Items) GetFormat() *low.NodeReference[string] { func (i *Items) GetFormat() *low.NodeReference[string] {
return &i.Format return &i.Format
} }
func (i *Items) GetItems() *low.NodeReference[any] { func (i *Items) GetItems() *low.NodeReference[any] {
k := low.NodeReference[any]{ k := low.NodeReference[any]{
KeyNode: i.Items.KeyNode, KeyNode: i.Items.KeyNode,
@@ -165,48 +138,63 @@ func (i *Items) GetItems() *low.NodeReference[any] {
} }
return &k return &k
} }
func (i *Items) GetCollectionFormat() *low.NodeReference[string] { func (i *Items) GetCollectionFormat() *low.NodeReference[string] {
return &i.CollectionFormat return &i.CollectionFormat
} }
func (i *Items) GetDescription() *low.NodeReference[string] { func (i *Items) GetDescription() *low.NodeReference[string] {
return nil // not implemented, but required to align with header contract return nil // not implemented, but required to align with header contract
} }
func (i *Items) GetDefault() *low.NodeReference[any] {
func (i *Items) GetDefault() *low.NodeReference[*yaml.Node] {
return &i.Default return &i.Default
} }
func (i *Items) GetMaximum() *low.NodeReference[int] { func (i *Items) GetMaximum() *low.NodeReference[int] {
return &i.Maximum return &i.Maximum
} }
func (i *Items) GetExclusiveMaximum() *low.NodeReference[bool] { func (i *Items) GetExclusiveMaximum() *low.NodeReference[bool] {
return &i.ExclusiveMaximum return &i.ExclusiveMaximum
} }
func (i *Items) GetMinimum() *low.NodeReference[int] { func (i *Items) GetMinimum() *low.NodeReference[int] {
return &i.Minimum return &i.Minimum
} }
func (i *Items) GetExclusiveMinimum() *low.NodeReference[bool] { func (i *Items) GetExclusiveMinimum() *low.NodeReference[bool] {
return &i.ExclusiveMinimum return &i.ExclusiveMinimum
} }
func (i *Items) GetMaxLength() *low.NodeReference[int] { func (i *Items) GetMaxLength() *low.NodeReference[int] {
return &i.MaxLength return &i.MaxLength
} }
func (i *Items) GetMinLength() *low.NodeReference[int] { func (i *Items) GetMinLength() *low.NodeReference[int] {
return &i.MinLength return &i.MinLength
} }
func (i *Items) GetPattern() *low.NodeReference[string] { func (i *Items) GetPattern() *low.NodeReference[string] {
return &i.Pattern return &i.Pattern
} }
func (i *Items) GetMaxItems() *low.NodeReference[int] { func (i *Items) GetMaxItems() *low.NodeReference[int] {
return &i.MaxItems return &i.MaxItems
} }
func (i *Items) GetMinItems() *low.NodeReference[int] { func (i *Items) GetMinItems() *low.NodeReference[int] {
return &i.MinItems return &i.MinItems
} }
func (i *Items) GetUniqueItems() *low.NodeReference[bool] { func (i *Items) GetUniqueItems() *low.NodeReference[bool] {
return &i.UniqueItems return &i.UniqueItems
} }
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[*yaml.Node]] {
return &i.Enum return &i.Enum
} }
func (i *Items) GetMultipleOf() *low.NodeReference[int] { func (i *Items) GetMultipleOf() *low.NodeReference[int] {
return &i.MultipleOf return &i.MultipleOf
} }

View File

@@ -5,15 +5,16 @@ package v2
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestItems_Build(t *testing.T) { func TestItems_Build(t *testing.T) {
yml := `items: yml := `items:
$ref: break` $ref: break`
@@ -31,7 +32,6 @@ func TestItems_Build(t *testing.T) {
} }
func TestItems_DefaultAsSlice(t *testing.T) { func TestItems_DefaultAsSlice(t *testing.T) {
yml := `x-thing: thing yml := `x-thing: thing
default: default:
- pizza - pizza
@@ -45,12 +45,14 @@ default:
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value, 2) var def []string
assert.Len(t, n.GetExtensions(), 1) _ = n.Default.Value.Decode(&def)
assert.Len(t, def, 2)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }
func TestItems_DefaultAsMap(t *testing.T) { func TestItems_DefaultAsMap(t *testing.T) {
yml := `default: yml := `default:
hot: pizza hot: pizza
tasty: beer` tasty: beer`
@@ -63,12 +65,13 @@ func TestItems_DefaultAsMap(t *testing.T) {
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value, 2) var def map[string]string
_ = n.Default.GetValue().Decode(&def)
assert.Len(t, def, 2)
} }
func TestItems_Hash_n_Grab(t *testing.T) { func TestItems_Hash_n_Grab(t *testing.T) {
yml := `type: string yml := `type: string
format: left format: left
collectionFormat: nice collectionFormat: nice
@@ -138,7 +141,10 @@ pattern: wow
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value) assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value)
var def string
_ = n.GetDefault().Value.Decode(&def)
assert.Equal(t, "shut that door!", def)
assert.Equal(t, 10, n.GetMaximum().Value) assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value) assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value) assert.True(t, n.GetExclusiveMinimum().Value)
@@ -152,7 +158,8 @@ pattern: wow
assert.Equal(t, "wow", n.GetPattern().Value) assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value) assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2) assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value)
assert.Nil(t, n.GetDescription())
var xBelly string
_ = n.FindExtension("x-belly").GetValue().Decode(&xBelly)
assert.Equal(t, "large", xBelly)
} }

View File

@@ -7,13 +7,15 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort"
"strings"
) )
// Operation represents a low-level Swagger / OpenAPI 2 Operation object. // Operation represents a low-level Swagger / OpenAPI 2 Operation object.
@@ -33,7 +35,7 @@ type Operation struct {
Schemes low.NodeReference[[]low.ValueReference[string]] Schemes low.NodeReference[[]low.ValueReference[string]]
Deprecated low.NodeReference[bool] Deprecated low.NodeReference[bool]
Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]] Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
// Build will extract external docs, extensions, parameters, responses and security requirements. // Build will extract external docs, extensions, parameters, responses and security requirements.
@@ -150,14 +152,7 @@ func (o *Operation) Hash() [32]byte {
} }
sort.Strings(keys) sort.Strings(keys)
f = append(f, keys...) f = append(f, keys...)
keys = make([]string, len(o.Extensions)) f = append(f, low.HashExtensions(o.Extensions)...)
z := 0
for k := range o.Extensions {
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, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }
@@ -185,7 +180,7 @@ func (o *Operation) GetOperationId() low.NodeReference[string] {
func (o *Operation) GetDeprecated() low.NodeReference[bool] { func (o *Operation) GetDeprecated() low.NodeReference[bool] {
return o.Deprecated return o.Deprecated
} }
func (o *Operation) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (o *Operation) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return o.Extensions return o.Extensions
} }
func (o *Operation) GetResponses() low.NodeReference[any] { func (o *Operation) GetResponses() low.NodeReference[any] {

View File

@@ -16,7 +16,6 @@ import (
) )
func TestOperation_Build_ExternalDocs(t *testing.T) { func TestOperation_Build_ExternalDocs(t *testing.T) {
yml := `externalDocs: yml := `externalDocs:
$ref: break` $ref: break`
@@ -31,11 +30,9 @@ func TestOperation_Build_ExternalDocs(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestOperation_Build_Params(t *testing.T) { func TestOperation_Build_Params(t *testing.T) {
yml := `parameters: yml := `parameters:
$ref: break` $ref: break`
@@ -50,11 +47,9 @@ func TestOperation_Build_Params(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestOperation_Build_Responses(t *testing.T) { func TestOperation_Build_Responses(t *testing.T) {
yml := `responses: yml := `responses:
$ref: break` $ref: break`
@@ -69,11 +64,9 @@ func TestOperation_Build_Responses(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestOperation_Build_Security(t *testing.T) { func TestOperation_Build_Security(t *testing.T) {
yml := `security: yml := `security:
$ref: break` $ref: break`
@@ -88,11 +81,9 @@ func TestOperation_Build_Security(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestOperation_Hash_n_Grab(t *testing.T) { func TestOperation_Hash_n_Grab(t *testing.T) {
yml := `tags: yml := `tags:
- nice - nice
- hat - hat
@@ -185,5 +176,5 @@ security:
assert.True(t, n.GetDeprecated().Value) assert.True(t, n.GetDeprecated().Value)
assert.Equal(t, 1, orderedmap.Len(n.GetResponses().Value.(*Responses).Codes)) assert.Equal(t, 1, orderedmap.Len(n.GetResponses().Value.(*Responses).Codes))
assert.Len(t, n.GetSecurity().Value, 1) assert.Len(t, n.GetSecurity().Value, 1)
assert.Len(t, n.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }

View File

@@ -7,13 +7,15 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort"
"strings"
) )
// Parameter represents a low-level Swagger / OpenAPI 2 Parameter object. // Parameter represents a low-level Swagger / OpenAPI 2 Parameter object.
@@ -68,7 +70,7 @@ type Parameter struct {
Schema low.NodeReference[*base.SchemaProxy] Schema low.NodeReference[*base.SchemaProxy]
Items low.NodeReference[*Items] Items low.NodeReference[*Items]
CollectionFormat low.NodeReference[string] CollectionFormat low.NodeReference[string]
Default low.NodeReference[any] Default low.NodeReference[*yaml.Node]
Maximum low.NodeReference[int] Maximum low.NodeReference[int]
ExclusiveMaximum low.NodeReference[bool] ExclusiveMaximum low.NodeReference[bool]
Minimum low.NodeReference[int] Minimum low.NodeReference[int]
@@ -79,18 +81,18 @@ type Parameter struct {
MaxItems low.NodeReference[int] MaxItems low.NodeReference[int]
MinItems low.NodeReference[int] MinItems low.NodeReference[int]
UniqueItems low.NodeReference[bool] UniqueItems low.NodeReference[bool]
Enum low.NodeReference[[]low.ValueReference[any]] Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
MultipleOf low.NodeReference[int] MultipleOf low.NodeReference[int]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
// FindExtension attempts to locate a extension value given a name. // FindExtension attempts to locate a extension value given a name.
func (p *Parameter) FindExtension(ext string) *low.ValueReference[any] { func (p *Parameter) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, p.Extensions) return low.FindItemInOrderedMap(ext, p.Extensions)
} }
// GetExtensions returns all Parameter extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Parameter extensions and satisfies the low.HasExtensions interface.
func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (p *Parameter) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return p.Extensions return p.Extensions
} }
@@ -114,30 +116,8 @@ func (p *Parameter) Build(ctx context.Context, _, root *yaml.Node, idx *index.Sp
_, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content)
if vn != nil { if vn != nil {
var n map[string]interface{} p.Default = low.NodeReference[*yaml.Node]{
err := vn.Decode(&n) Value: vn,
if err != nil {
var k []interface{}
err = vn.Decode(&k)
if err != nil {
var j interface{}
_ = vn.Decode(&j)
p.Default = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
p.Default = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
p.Default = low.NodeReference[any]{
Value: n,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
@@ -172,8 +152,8 @@ func (p *Parameter) Hash() [32]byte {
if p.CollectionFormat.Value != "" { if p.CollectionFormat.Value != "" {
f = append(f, p.CollectionFormat.Value) f = append(f, p.CollectionFormat.Value)
} }
if p.Default.Value != "" { if p.Default.Value != nil && !p.Default.Value.IsZero() {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(p.Default.Value))))) f = append(f, low.GenerateHashString(p.Default.Value))
} }
f = append(f, fmt.Sprint(p.Maximum.Value)) f = append(f, fmt.Sprint(p.Maximum.Value))
f = append(f, fmt.Sprint(p.Minimum.Value)) f = append(f, fmt.Sprint(p.Minimum.Value))
@@ -192,20 +172,13 @@ func (p *Parameter) Hash() [32]byte {
keys := make([]string, len(p.Enum.Value)) keys := make([]string, len(p.Enum.Value))
z := 0 z := 0
for k := range p.Enum.Value { for k := range p.Enum.Value {
keys[z] = fmt.Sprint(p.Enum.Value[k].Value) keys[z] = low.ValueToString(p.Enum.Value[k].Value)
z++ z++
} }
sort.Strings(keys) sort.Strings(keys)
f = append(f, keys...) f = append(f, keys...)
keys = make([]string, len(p.Extensions)) f = append(f, low.HashExtensions(p.Extensions)...)
z = 0
for k := range p.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(p.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
if p.Items.Value != nil { if p.Items.Value != nil {
f = append(f, fmt.Sprintf("%x", p.Items.Value.Hash())) f = append(f, fmt.Sprintf("%x", p.Items.Value.Hash()))
} }
@@ -217,21 +190,27 @@ func (p *Parameter) Hash() [32]byte {
func (p *Parameter) GetName() *low.NodeReference[string] { func (p *Parameter) GetName() *low.NodeReference[string] {
return &p.Name return &p.Name
} }
func (p *Parameter) GetIn() *low.NodeReference[string] { func (p *Parameter) GetIn() *low.NodeReference[string] {
return &p.In return &p.In
} }
func (p *Parameter) GetType() *low.NodeReference[string] { func (p *Parameter) GetType() *low.NodeReference[string] {
return &p.Type return &p.Type
} }
func (p *Parameter) GetDescription() *low.NodeReference[string] { func (p *Parameter) GetDescription() *low.NodeReference[string] {
return &p.Description return &p.Description
} }
func (p *Parameter) GetRequired() *low.NodeReference[bool] { func (p *Parameter) GetRequired() *low.NodeReference[bool] {
return &p.Required return &p.Required
} }
func (p *Parameter) GetAllowEmptyValue() *low.NodeReference[bool] { func (p *Parameter) GetAllowEmptyValue() *low.NodeReference[bool] {
return &p.AllowEmptyValue return &p.AllowEmptyValue
} }
func (p *Parameter) GetSchema() *low.NodeReference[any] { func (p *Parameter) GetSchema() *low.NodeReference[any] {
i := low.NodeReference[any]{ i := low.NodeReference[any]{
KeyNode: p.Schema.KeyNode, KeyNode: p.Schema.KeyNode,
@@ -240,9 +219,11 @@ func (p *Parameter) GetSchema() *low.NodeReference[any] {
} }
return &i return &i
} }
func (p *Parameter) GetFormat() *low.NodeReference[string] { func (p *Parameter) GetFormat() *low.NodeReference[string] {
return &p.Format return &p.Format
} }
func (p *Parameter) GetItems() *low.NodeReference[any] { func (p *Parameter) GetItems() *low.NodeReference[any] {
i := low.NodeReference[any]{ i := low.NodeReference[any]{
KeyNode: p.Items.KeyNode, KeyNode: p.Items.KeyNode,
@@ -251,45 +232,59 @@ func (p *Parameter) GetItems() *low.NodeReference[any] {
} }
return &i return &i
} }
func (p *Parameter) GetCollectionFormat() *low.NodeReference[string] { func (p *Parameter) GetCollectionFormat() *low.NodeReference[string] {
return &p.CollectionFormat return &p.CollectionFormat
} }
func (p *Parameter) GetDefault() *low.NodeReference[any] {
func (p *Parameter) GetDefault() *low.NodeReference[*yaml.Node] {
return &p.Default return &p.Default
} }
func (p *Parameter) GetMaximum() *low.NodeReference[int] { func (p *Parameter) GetMaximum() *low.NodeReference[int] {
return &p.Maximum return &p.Maximum
} }
func (p *Parameter) GetExclusiveMaximum() *low.NodeReference[bool] { func (p *Parameter) GetExclusiveMaximum() *low.NodeReference[bool] {
return &p.ExclusiveMaximum return &p.ExclusiveMaximum
} }
func (p *Parameter) GetMinimum() *low.NodeReference[int] { func (p *Parameter) GetMinimum() *low.NodeReference[int] {
return &p.Minimum return &p.Minimum
} }
func (p *Parameter) GetExclusiveMinimum() *low.NodeReference[bool] { func (p *Parameter) GetExclusiveMinimum() *low.NodeReference[bool] {
return &p.ExclusiveMinimum return &p.ExclusiveMinimum
} }
func (p *Parameter) GetMaxLength() *low.NodeReference[int] { func (p *Parameter) GetMaxLength() *low.NodeReference[int] {
return &p.MaxLength return &p.MaxLength
} }
func (p *Parameter) GetMinLength() *low.NodeReference[int] { func (p *Parameter) GetMinLength() *low.NodeReference[int] {
return &p.MinLength return &p.MinLength
} }
func (p *Parameter) GetPattern() *low.NodeReference[string] { func (p *Parameter) GetPattern() *low.NodeReference[string] {
return &p.Pattern return &p.Pattern
} }
func (p *Parameter) GetMaxItems() *low.NodeReference[int] { func (p *Parameter) GetMaxItems() *low.NodeReference[int] {
return &p.MaxItems return &p.MaxItems
} }
func (p *Parameter) GetMinItems() *low.NodeReference[int] { func (p *Parameter) GetMinItems() *low.NodeReference[int] {
return &p.MinItems return &p.MinItems
} }
func (p *Parameter) GetUniqueItems() *low.NodeReference[bool] { func (p *Parameter) GetUniqueItems() *low.NodeReference[bool] {
return &p.UniqueItems return &p.UniqueItems
} }
func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[*yaml.Node]] {
return &p.Enum return &p.Enum
} }
func (p *Parameter) GetMultipleOf() *low.NodeReference[int] { func (p *Parameter) GetMultipleOf() *low.NodeReference[int] {
return &p.MultipleOf return &p.MultipleOf
} }

View File

@@ -5,16 +5,17 @@ package v2
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestParameter_Build(t *testing.T) { func TestParameter_Build(t *testing.T) {
yml := `$ref: break` yml := `$ref: break`
var idxNode yaml.Node var idxNode yaml.Node
@@ -28,11 +29,9 @@ func TestParameter_Build(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestParameter_Build_Items(t *testing.T) { func TestParameter_Build_Items(t *testing.T) {
yml := `items: yml := `items:
$ref: break` $ref: break`
@@ -47,11 +46,9 @@ func TestParameter_Build_Items(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestParameter_DefaultSlice(t *testing.T) { func TestParameter_DefaultSlice(t *testing.T) {
yml := `default: yml := `default:
- things - things
- junk - junk
@@ -65,11 +62,14 @@ func TestParameter_DefaultSlice(t *testing.T) {
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value.([]any), 3)
var a []any
_ = n.Default.Value.Decode(&a)
assert.Len(t, a, 3)
} }
func TestParameter_DefaultMap(t *testing.T) { func TestParameter_DefaultMap(t *testing.T) {
yml := `default: yml := `default:
things: junk things: junk
stuff: more junk` stuff: more junk`
@@ -82,11 +82,14 @@ func TestParameter_DefaultMap(t *testing.T) {
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value.(map[string]any), 2)
var m map[string]any
_ = n.Default.Value.Decode(&m)
assert.Len(t, m, 2)
} }
func TestParameter_NoDefaultNoError(t *testing.T) { func TestParameter_NoDefaultNoError(t *testing.T) {
yml := `name: pizza-pie` yml := `name: pizza-pie`
var idxNode yaml.Node var idxNode yaml.Node
@@ -101,7 +104,6 @@ func TestParameter_NoDefaultNoError(t *testing.T) {
} }
func TestParameter_Hash_n_Grab(t *testing.T) { func TestParameter_Hash_n_Grab(t *testing.T) {
yml := `name: mcmuffin yml := `name: mcmuffin
in: my-belly in: my-belly
description: tasty! description: tasty!
@@ -185,7 +187,10 @@ allowEmptyValue: true
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value) assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value)
var def string
_ = n.GetDefault().Value.Decode(&def)
assert.Equal(t, "shut that door!", def)
assert.Equal(t, 10, n.GetMaximum().Value) assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value) assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value) assert.True(t, n.GetExclusiveMinimum().Value)
@@ -199,7 +204,10 @@ allowEmptyValue: true
assert.Equal(t, "wow", n.GetPattern().Value) assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value) assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2) assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value)
var xBelly string
_ = n.FindExtension("x-belly").Value.Decode(&xBelly)
assert.Equal(t, "large", xBelly)
assert.Equal(t, "tasty!", n.GetDescription().Value) assert.Equal(t, "tasty!", n.GetDescription().Value)
assert.Equal(t, "mcmuffin", n.GetName().Value) assert.Equal(t, "mcmuffin", n.GetName().Value)
assert.Equal(t, "my-belly", n.GetIn().Value) assert.Equal(t, "my-belly", n.GetIn().Value)
@@ -208,6 +216,5 @@ allowEmptyValue: true
assert.Equal(t, "int", v.Value.A) // A is v2 assert.Equal(t, "int", v.Value.A) // A is v2
assert.True(t, n.GetRequired().Value) assert.True(t, n.GetRequired().Value)
assert.True(t, n.GetAllowEmptyValue().Value) assert.True(t, n.GetAllowEmptyValue().Value)
assert.Len(t, n.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }

View File

@@ -13,6 +13,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -34,16 +35,16 @@ type PathItem struct {
Head low.NodeReference[*Operation] Head low.NodeReference[*Operation]
Patch low.NodeReference[*Operation] Patch low.NodeReference[*Operation]
Parameters low.NodeReference[[]low.ValueReference[*Parameter]] Parameters low.NodeReference[[]low.ValueReference[*Parameter]]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
} }
// FindExtension will attempt to locate an extension given a name. // FindExtension will attempt to locate an extension given a name.
func (p *PathItem) FindExtension(ext string) *low.ValueReference[any] { func (p *PathItem) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInMap[any](ext, p.Extensions) return low.FindItemInOrderedMap(ext, p.Extensions)
} }
// GetExtensions returns all PathItem extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all PathItem extensions and satisfies the low.HasExtensions interface.
func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (p *PathItem) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return p.Extensions return p.Extensions
} }
@@ -153,8 +154,8 @@ func (p *PathItem) Build(ctx context.Context, _, root *yaml.Node, idx *index.Spe
} }
} }
//all operations have been superficially built, // all operations have been superficially built,
//now we need to build out the operation, we will do this asynchronously for speed. // now we need to build out the operation, we will do this asynchronously for speed.
opBuildChan := make(chan bool) opBuildChan := make(chan bool)
opErrorChan := make(chan error) opErrorChan := make(chan error)
@@ -223,13 +224,6 @@ func (p *PathItem) Hash() [32]byte {
} }
sort.Strings(keys) sort.Strings(keys)
f = append(f, keys...) f = append(f, keys...)
keys = make([]string, len(p.Extensions)) f = append(f, low.HashExtensions(p.Extensions)...)
z := 0
for k := range p.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(p.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -5,15 +5,16 @@ package v2
import ( import (
"context" "context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestPathItem_Build_Params(t *testing.T) { func TestPathItem_Build_Params(t *testing.T) {
yml := `parameters: yml := `parameters:
$ref: break` $ref: break`
@@ -28,11 +29,9 @@ func TestPathItem_Build_Params(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestPathItem_Build_MethodFail(t *testing.T) { func TestPathItem_Build_MethodFail(t *testing.T) {
yml := `post: yml := `post:
$ref: break` $ref: break`
@@ -47,11 +46,9 @@ func TestPathItem_Build_MethodFail(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestPathItem_Hash(t *testing.T) { func TestPathItem_Hash(t *testing.T) {
yml := `get: yml := `get:
description: get me up description: get me up
put: put:
@@ -108,6 +105,5 @@ parameters:
// hash // hash
assert.Equal(t, n.Hash(), n2.Hash()) assert.Equal(t, n.Hash(), n2.Hash())
assert.Len(t, n.GetExtensions(), 1) assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
} }

Some files were not shown because too many files have changed in this diff Show More