Added support for libopenapi-validator and bumped coverage.

Non breaking changes add support for the new `libopenapi-validator` module.

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2023-04-22 09:29:16 -04:00
parent 9edb995d12
commit 3055711f24
17 changed files with 2179 additions and 1928 deletions

View File

@@ -16,7 +16,7 @@ type DocumentConfiguration struct {
BaseURL *url.URL BaseURL *url.URL
// If resolving locally, the BasePath will be the root from which relative references will be resolved from. // If resolving locally, the BasePath will be the root from which relative references will be resolved from.
// It's usually the location of the root specification. // It's usually the location of the root specification.
BasePath string // set the Base Path for resolving relative references if the spec is exploded. BasePath string // set the Base Path for resolving relative references if the spec is exploded.
// AllowFileReferences will allow the index to locate relative file references. This is disabled by default. // AllowFileReferences will allow the index to locate relative file references. This is disabled by default.
@@ -27,15 +27,15 @@ type DocumentConfiguration struct {
} }
func NewOpenDocumentConfiguration() *DocumentConfiguration { func NewOpenDocumentConfiguration() *DocumentConfiguration {
return &DocumentConfiguration{ return &DocumentConfiguration{
AllowFileReferences: true, AllowFileReferences: true,
AllowRemoteReferences: true, AllowRemoteReferences: true,
} }
} }
func NewClosedDocumentConfiguration() *DocumentConfiguration { func NewClosedDocumentConfiguration() *DocumentConfiguration {
return &DocumentConfiguration{ return &DocumentConfiguration{
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
} }
} }

View File

@@ -18,9 +18,10 @@ import (
// The N value is a bit to make it each to know which value (A or B) is used, this prevents having to // The N value is a bit to make it each to know which value (A or B) is used, this prevents having to
// if/else on the value to determine which one is set. // if/else on the value to determine which one is set.
type DynamicValue[A any, B any] struct { type DynamicValue[A any, B any] struct {
N int // 0 == A, 1 == B N int // 0 == A, 1 == B
A A A A
B B B B
inline bool
} }
// IsA will return true if the 'A' or left value is set. (OpenAPI 3) // IsA will return true if the 'A' or left value is set. (OpenAPI 3)
@@ -34,9 +35,16 @@ func (d *DynamicValue[A, B]) IsB() bool {
} }
func (d *DynamicValue[A, B]) Render() ([]byte, error) { func (d *DynamicValue[A, B]) Render() ([]byte, error) {
d.inline = false
return yaml.Marshal(d) return yaml.Marshal(d)
} }
func (d *DynamicValue[A, B]) RenderInline() ([]byte, error) {
d.inline = true
return yaml.Marshal(d)
}
// MarshalYAML will create a ready to render YAML representation of the DynamicValue object. // MarshalYAML will create a ready to render YAML representation of the DynamicValue object.
func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) { func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
// this is a custom renderer, we can't use the NodeBuilder out of the gate. // this is a custom renderer, we can't use the NodeBuilder out of the gate.
@@ -53,10 +61,18 @@ func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
to := reflect.TypeOf(value) to := reflect.TypeOf(value)
switch to.Kind() { switch to.Kind() {
case reflect.Ptr: case reflect.Ptr:
if r, ok := value.(high.Renderable); ok { if d.inline {
return r.MarshalYAML() if r, ok := value.(high.RenderableInline); ok {
return r.MarshalYAMLInline()
} else {
_ = n.Encode(value)
}
} else { } else {
_ = n.Encode(value) if r, ok := value.(high.Renderable); ok {
return r.MarshalYAML()
} else {
_ = n.Encode(value)
}
} }
case reflect.Bool: case reflect.Bool:
_ = n.Encode(value.(bool)) _ = n.Encode(value.(bool))
@@ -77,42 +93,9 @@ func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
return &n, err return &n, err
} }
// MarshalYAML will create a ready to render YAML representation of the DynamicValue object. // MarshalYAMLInline will create a ready to render YAML representation of the DynamicValue object. The
// references will be inlined instead of kept as references.
func (d *DynamicValue[A, B]) MarshalYAMLInline() (interface{}, error) { func (d *DynamicValue[A, B]) MarshalYAMLInline() (interface{}, error) {
// this is a custom renderer, we can't use the NodeBuilder out of the gate. d.inline = true
var n yaml.Node return d.MarshalYAML()
var err error
var value any
if d.IsA() {
value = d.A
}
if d.IsB() {
value = d.B
}
to := reflect.TypeOf(value)
switch to.Kind() {
case reflect.Ptr:
if r, ok := value.(high.RenderableInline); ok {
return r.MarshalYAMLInline()
} else {
_ = n.Encode(value)
}
case reflect.Bool:
_ = n.Encode(value.(bool))
case reflect.Int:
_ = n.Encode(value.(int))
case reflect.String:
_ = n.Encode(value.(string))
case reflect.Int64:
_ = n.Encode(value.(int64))
case reflect.Float64:
_ = n.Encode(value.(float64))
case reflect.Float32:
_ = n.Encode(value.(float32))
case reflect.Int32:
_ = n.Encode(value.(int32))
}
return &n, err
} }

View File

@@ -4,71 +4,172 @@
package base package base
import ( import (
"github.com/stretchr/testify/assert" "github.com/pb33f/libopenapi/datamodel/low"
"strings" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"testing" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
) )
func TestDynamicValue_Render_A(t *testing.T) { func TestDynamicValue_Render_A(t *testing.T) {
dv := &DynamicValue[string, int]{N: 0, A: "hello"} dv := &DynamicValue[string, int]{N: 0, A: "hello"}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "hello", strings.TrimSpace(string(dvb))) assert.Equal(t, "hello", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_B(t *testing.T) { func TestDynamicValue_Render_B(t *testing.T) {
dv := &DynamicValue[string, int]{N: 1, B: 12345} dv := &DynamicValue[string, int]{N: 1, B: 12345}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "12345", strings.TrimSpace(string(dvb))) assert.Equal(t, "12345", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_Bool(t *testing.T) { func TestDynamicValue_Render_Bool(t *testing.T) {
dv := &DynamicValue[string, bool]{N: 1, B: true} dv := &DynamicValue[string, bool]{N: 1, B: true}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "true", strings.TrimSpace(string(dvb))) assert.Equal(t, "true", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_Int64(t *testing.T) { func TestDynamicValue_Render_Int64(t *testing.T) {
dv := &DynamicValue[string, int64]{N: 1, B: 12345567810} dv := &DynamicValue[string, int64]{N: 1, B: 12345567810}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "12345567810", strings.TrimSpace(string(dvb))) assert.Equal(t, "12345567810", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_Int32(t *testing.T) { func TestDynamicValue_Render_Int32(t *testing.T) {
dv := &DynamicValue[string, int32]{N: 1, B: 1234567891} dv := &DynamicValue[string, int32]{N: 1, B: 1234567891}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "1234567891", strings.TrimSpace(string(dvb))) assert.Equal(t, "1234567891", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_Float32(t *testing.T) { func TestDynamicValue_Render_Float32(t *testing.T) {
dv := &DynamicValue[string, float32]{N: 1, B: 23456.123} dv := &DynamicValue[string, float32]{N: 1, B: 23456.123}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "23456.123", strings.TrimSpace(string(dvb))) assert.Equal(t, "23456.123", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_Float64(t *testing.T) { func TestDynamicValue_Render_Float64(t *testing.T) {
dv := &DynamicValue[string, float64]{N: 1, B: 23456.1233456778} dv := &DynamicValue[string, float64]{N: 1, B: 23456.1233456778}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "23456.1233456778", strings.TrimSpace(string(dvb))) assert.Equal(t, "23456.1233456778", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_Ptr(t *testing.T) { func TestDynamicValue_Render_Ptr(t *testing.T) {
type cake struct { type cake struct {
Cake string Cake string
} }
dv := &DynamicValue[string, *cake]{N: 1, B: &cake{Cake: "vanilla"}} dv := &DynamicValue[string, *cake]{N: 1, B: &cake{Cake: "vanilla"}}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "cake: vanilla", strings.TrimSpace(string(dvb))) assert.Equal(t, "cake: vanilla", strings.TrimSpace(string(dvb)))
} }
func TestDynamicValue_Render_PtrRenderable(t *testing.T) { func TestDynamicValue_Render_PtrRenderable(t *testing.T) {
tag := &Tag{ tag := &Tag{
Name: "cake", Name: "cake",
} }
dv := &DynamicValue[string, *Tag]{N: 1, B: tag} dv := &DynamicValue[string, *Tag]{N: 1, B: tag}
dvb, _ := dv.Render() dvb, _ := dv.Render()
assert.Equal(t, "name: cake", strings.TrimSpace(string(dvb))) assert.Equal(t, "name: cake", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_RenderInline(t *testing.T) {
tag := &Tag{
Name: "cake",
}
dv := &DynamicValue[string, *Tag]{N: 1, B: tag}
dvb, _ := dv.RenderInline()
assert.Equal(t, "name: cake", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_MarshalYAMLInline(t *testing.T) {
const ymlComponents = `components:
schemas:
rice:
type: array
items:
$ref: '#/components/schemas/ice'
nice:
properties:
rice:
$ref: '#/components/schemas/rice'
ice:
type: string`
idx := func() *index.SpecIndex {
var idxNode yaml.Node
err := yaml.Unmarshal([]byte(ymlComponents), &idxNode)
assert.NoError(t, err)
return index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
}()
const ref = "#/components/schemas/nice"
const ymlSchema = `$ref: '` + ref + `'`
var node yaml.Node
_ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(node.Content[0], idx)
assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{
Value: lowProxy,
}
sp := NewSchemaProxy(&lowRef)
rend, _ := sp.MarshalYAMLInline()
// convert node into yaml
bits, _ := yaml.Marshal(rend)
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) {
const ymlComponents = `components:
schemas:
rice:
type: array
items:
$ref: '#/components/schemas/bork'
nice:
properties:
rice:
$ref: '#/components/schemas/berk'
ice:
type: string`
idx := func() *index.SpecIndex {
var idxNode yaml.Node
err := yaml.Unmarshal([]byte(ymlComponents), &idxNode)
assert.NoError(t, err)
return index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
}()
const ref = "#/components/schemas/nice"
const ymlSchema = `$ref: '` + ref + `'`
var node yaml.Node
_ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(node.Content[0], idx)
assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{
Value: lowProxy,
}
sp := NewSchemaProxy(&lowRef)
rend, er := sp.MarshalYAMLInline()
assert.Nil(t, rend)
assert.Error(t, er)
} }

View File

@@ -6,9 +6,7 @@ package base
import ( import (
"fmt" "fmt"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sync" "sync"
"gopkg.in/yaml.v3"
"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"
@@ -70,7 +68,10 @@ type Schema struct {
// in 3.1 Items can be a Schema or a boolean // in 3.1 Items can be a Schema or a boolean
Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"` Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"`
// 3.1 only, part of the JSON Schema spec provides a way to identify a subschema // 3.1 only, part of the JSON Schema spec provides a way to identify a subschema
Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"`
// Compatible with all versions
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"` Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Properties map[string]*SchemaProxy `json:"properties,omitempty" yaml:"properties,omitempty"` Properties 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"`
@@ -97,42 +98,10 @@ type Schema struct {
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 any `json:"example,omitempty" yaml:"example,omitempty"`
Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions map[string]any `json:"-" yaml:"-"`
low *base.Schema low *base.Schema
// Compatible with all versions
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Properties map[string]*SchemaProxy `json:"properties,omitempty" yaml:"properties,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
MultipleOf *int64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *int64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
Minimum *int64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
UniqueItems *int64 `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"`
Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
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.
ParentProxy *SchemaProxy `json:"-" yaml:"-"` ParentProxy *SchemaProxy `json:"-" yaml:"-"`
} }
@@ -311,9 +280,11 @@ func NewSchema(schema *base.Schema) *Schema {
s.Required = req s.Required = req
var enum []any var enum []any
if !schema.Anchor.IsEmpty() { if !schema.Anchor.IsEmpty() {
s.Anchor = schema.Anchor.Value s.Anchor = schema.Anchor.Value
} }
// TODO: check this behavior.
for i := range schema.Enum.Value { for i := range schema.Enum.Value {
enum = append(enum, fmt.Sprint(schema.Enum.Value[i].Value)) enum = append(enum, fmt.Sprint(schema.Enum.Value[i].Value))
} }

View File

@@ -4,11 +4,11 @@
package base package base
import ( import (
"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/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// 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
@@ -36,8 +36,8 @@ import (
// and slow things down when building. By preventing recursion through every polymorphic item, building models is kept // and slow things down when building. By preventing recursion through every polymorphic item, building models is kept
// fast and snappy, which is desired for realtime processing of specs. // fast and snappy, which is desired for realtime processing of specs.
// //
// - Q: Yeah, but, why not just use state to avoiding re-visiting seen polymorphic nodes? // - Q: Yeah, but, why not just use state to avoiding re-visiting seen polymorphic nodes?
// - A: It's slow, takes up memory and still has runaway potential in very, very long chains. // - A: It's slow, takes up memory and still has runaway potential in very, very long chains.
// //
// 3. Short Circuit Errors. // 3. Short Circuit Errors.
// //
@@ -45,133 +45,133 @@ 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 {
schema *low.NodeReference[*base.SchemaProxy] schema *low.NodeReference[*base.SchemaProxy]
buildError error buildError error
rendered *Schema rendered *Schema
refStr string refStr string
} }
// NewSchemaProxy creates a new high-level SchemaProxy from a low-level one. // NewSchemaProxy creates a new high-level SchemaProxy from a low-level one.
func NewSchemaProxy(schema *low.NodeReference[*base.SchemaProxy]) *SchemaProxy { func NewSchemaProxy(schema *low.NodeReference[*base.SchemaProxy]) *SchemaProxy {
return &SchemaProxy{schema: schema} return &SchemaProxy{schema: schema}
} }
// CreateSchemaProxy will create a new high-level SchemaProxy from a high-level Schema, this acts the same // CreateSchemaProxy will create a new high-level SchemaProxy from a high-level Schema, this acts the same
// as if the SchemaProxy is pre-rendered. // as if the SchemaProxy is pre-rendered.
func CreateSchemaProxy(schema *Schema) *SchemaProxy { func CreateSchemaProxy(schema *Schema) *SchemaProxy {
return &SchemaProxy{rendered: schema} return &SchemaProxy{rendered: schema}
} }
// CreateSchemaProxyRef will create a new high-level SchemaProxy from a reference string, this is used only when // CreateSchemaProxyRef will create a new high-level SchemaProxy from a reference string, this is used only when
// building out new models from scratch that require a reference rather than a schema implementation. // building out new models from scratch that require a reference rather than a schema implementation.
func CreateSchemaProxyRef(ref string) *SchemaProxy { func CreateSchemaProxyRef(ref string) *SchemaProxy {
return &SchemaProxy{refStr: ref} return &SchemaProxy{refStr: ref}
} }
// Schema will create a new Schema instance using NewSchema from the low-level SchemaProxy backing this high-level one. // Schema will create a new Schema instance using NewSchema from the low-level SchemaProxy backing this high-level one.
// If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access // If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access
// to that building error. // to that building error.
func (sp *SchemaProxy) Schema() *Schema { func (sp *SchemaProxy) Schema() *Schema {
if sp.rendered == nil { if sp.rendered == nil {
s := sp.schema.Value.Schema() s := sp.schema.Value.Schema()
if s == nil { if s == nil {
sp.buildError = sp.schema.Value.GetBuildError() sp.buildError = sp.schema.Value.GetBuildError()
return nil return nil
} }
sch := NewSchema(s) sch := NewSchema(s)
sch.ParentProxy = sp sch.ParentProxy = sp
sp.rendered = sch sp.rendered = sch
return sch return sch
} else { } else {
return sp.rendered return sp.rendered
} }
} }
// 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.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.IsSchemaReference()
} }
return false return false
} }
// GetReference returns the location of the $ref if this SchemaProxy is a reference to another Schema. // GetReference returns the location of the $ref if this SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) GetReference() string { 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.Value.GetSchemaReference()
} }
// BuildSchema operates the same way as Schema, except it will return any error along with the *Schema // BuildSchema operates the same way as Schema, except it will return any error along with the *Schema
func (sp *SchemaProxy) BuildSchema() (*Schema, error) { func (sp *SchemaProxy) BuildSchema() (*Schema, error) {
if sp.rendered != nil { if sp.rendered != nil {
return sp.rendered, sp.buildError return sp.rendered, sp.buildError
} }
schema := sp.Schema() schema := sp.Schema()
er := sp.buildError er := sp.buildError
return schema, er return schema, er
} }
// GetBuildError returns any error that was thrown when calling Schema() // GetBuildError returns any error that was thrown when calling Schema()
func (sp *SchemaProxy) GetBuildError() error { func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError return sp.buildError
} }
func (sp *SchemaProxy) GoLow() *base.SchemaProxy { func (sp *SchemaProxy) GoLow() *base.SchemaProxy {
if sp.schema == nil { if sp.schema == nil {
return nil return nil
} }
return sp.schema.Value return sp.schema.Value
} }
func (sp *SchemaProxy) GoLowUntyped() any { func (sp *SchemaProxy) GoLowUntyped() any {
if sp.schema == nil { if sp.schema == nil {
return nil return nil
} }
return sp.schema.Value return sp.schema.Value
} }
// Render will return a YAML representation of the Schema object as a byte slice. // Render will return a YAML representation of the Schema object as a byte slice.
func (sp *SchemaProxy) Render() ([]byte, error) { func (sp *SchemaProxy) Render() ([]byte, error) {
return yaml.Marshal(sp) return yaml.Marshal(sp)
} }
// MarshalYAML will create a ready to render YAML representation of the ExternalDoc object. // MarshalYAML will create a ready to render YAML representation of the ExternalDoc object.
func (sp *SchemaProxy) MarshalYAML() (interface{}, error) { func (sp *SchemaProxy) MarshalYAML() (interface{}, error) {
var s *Schema var s *Schema
var err error var err error
// if this schema isn't a reference, then build it out. // if this schema isn't a reference, then build it out.
if !sp.IsReference() { if !sp.IsReference() {
s, err = sp.BuildSchema() s, err = sp.BuildSchema()
if err != nil { if err != nil {
return nil, err return nil, err
} }
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil return nb.Render(), nil
} else { } else {
// do not build out a reference, just marshal the reference. // do not build out a reference, just marshal the reference.
mp := utils.CreateEmptyMapNode() mp := utils.CreateEmptyMapNode()
mp.Content = append(mp.Content, mp.Content = append(mp.Content,
utils.CreateStringNode("$ref"), utils.CreateStringNode("$ref"),
utils.CreateStringNode(sp.GetReference())) utils.CreateStringNode(sp.GetReference()))
return mp, nil return mp, nil
} }
} }
// MarshalYAMLInline will create a ready to render YAML representation of the ExternalDoc object. All of the // MarshalYAMLInline will create a ready to render YAML representation of the ExternalDoc object. The
// $ref values will be inlined instead of kept as is. // $ref values will be inlined instead of kept as is.
func (sp *SchemaProxy) MarshalYAMLInline() (interface{}, error) { func (sp *SchemaProxy) MarshalYAMLInline() (interface{}, error) {
var s *Schema var s *Schema
var err error var err error
s, err = sp.BuildSchema() s, err = sp.BuildSchema()
if err != nil { if err != nil {
return nil, err return nil, err
} }
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
nb.Resolve = true nb.Resolve = true
return nb.Render(), nil return nb.Render(), nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,59 +4,59 @@
package base 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"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// Tag represents a high-level Tag instance that is backed by a low-level one. // Tag represents a high-level Tag instance that is backed by a low-level one.
// //
// Adds metadata to a single tag that is used by the Operation Object. It is not mandatory to have a Tag Object per // Adds metadata to a single tag that is used by the Operation Object. It is not mandatory to have a Tag Object per
// tag defined in the Operation Object instances. // tag defined in the Operation Object instances.
// - v2: https://swagger.io/specification/v2/#tagObject // - v2: https://swagger.io/specification/v2/#tagObject
// - v3: https://swagger.io/specification/#tag-object // - v3: https://swagger.io/specification/#tag-object
type Tag struct { 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 map[string]any
low *low.Tag low *low.Tag
} }
// NewTag creates a new high-level Tag instance that is backed by a low-level one. // NewTag creates a new high-level Tag instance that is backed by a low-level one.
func NewTag(tag *low.Tag) *Tag { func NewTag(tag *low.Tag) *Tag {
t := new(Tag) t := new(Tag)
t.low = tag t.low = tag
if !tag.Name.IsEmpty() { if !tag.Name.IsEmpty() {
t.Name = tag.Name.Value t.Name = tag.Name.Value
} }
if !tag.Description.IsEmpty() { if !tag.Description.IsEmpty() {
t.Description = tag.Description.Value t.Description = tag.Description.Value
} }
if !tag.ExternalDocs.IsEmpty() { if !tag.ExternalDocs.IsEmpty() {
t.ExternalDocs = NewExternalDoc(tag.ExternalDocs.Value) t.ExternalDocs = NewExternalDoc(tag.ExternalDocs.Value)
} }
t.Extensions = high.ExtractExtensions(tag.Extensions) t.Extensions = high.ExtractExtensions(tag.Extensions)
return t return t
} }
// GoLow returns the low-level Tag instance used to create the high-level one. // GoLow returns the low-level Tag instance used to create the high-level one.
func (t *Tag) GoLow() *low.Tag { func (t *Tag) GoLow() *low.Tag {
return t.low return t.low
} }
// GoLowUntyped will return the low-level Tag instance that was used to create the high-level one, with no type // GoLowUntyped will return the low-level Tag instance that was used to create the high-level one, with no type
func (t *Tag) GoLowUntyped() any { func (t *Tag) GoLowUntyped() any {
return t.low return t.low
} }
// Render will return a YAML representation of the Info object as a byte slice. // Render will return a YAML representation of the Info object as a byte slice.
func (t *Tag) Render() ([]byte, error) { func (t *Tag) Render() ([]byte, error) {
return yaml.Marshal(t) return yaml.Marshal(t)
} }
// MarshalYAML will create a ready to render YAML representation of the Info object. // MarshalYAML will create a ready to render YAML representation of the Info object.
func (t *Tag) MarshalYAML() (interface{}, error) { func (t *Tag) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(t, t.low) nb := high.NewNodeBuilder(t, t.low)
return nb.Render(), nil return nb.Render(), nil
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ import (
// //
// For computing links, and providing instructions to execute them, a runtime expression is used for accessing values // For computing links, and providing instructions to execute them, a runtime expression is used for accessing values
// in an operation and using them as parameters while invoking the linked operation. // in an operation and using them as parameters while invoking the linked operation.
// - https://spec.openapis.org/oas/v3.1.0#link-object // - https://spec.openapis.org/oas/v3.1.0#link-object
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"`
@@ -71,4 +71,4 @@ func (l *Link) Render() ([]byte, error) {
func (l *Link) MarshalYAML() (interface{}, error) { func (l *Link) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(l, l.low) nb := high.NewNodeBuilder(l, l.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -4,114 +4,114 @@
package v3 package v3
import ( 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/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one. // Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one.
// //
// A unique parameter is defined by a combination of a name and location. // A unique parameter is defined by a combination of a name and location.
// - https://spec.openapis.org/oas/v3.1.0#parameter-object // - https://spec.openapis.org/oas/v3.1.0#parameter-object
type Parameter struct { 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,omitempty" yaml:"required,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,omitempty" yaml:"explode,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 any `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"` Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"`
Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"` Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions map[string]any `json:"-" yaml:"-"`
low *low.Parameter low *low.Parameter
} }
// NewParameter will create a new high-level instance of a Parameter, using a low-level one. // NewParameter will create a new high-level instance of a Parameter, using a low-level one.
func NewParameter(param *low.Parameter) *Parameter { func NewParameter(param *low.Parameter) *Parameter {
p := new(Parameter) p := new(Parameter)
p.low = param p.low = param
p.Name = param.Name.Value p.Name = param.Name.Value
p.In = param.In.Value p.In = param.In.Value
p.Description = param.Description.Value p.Description = param.Description.Value
p.Deprecated = param.Deprecated.Value p.Deprecated = param.Deprecated.Value
p.AllowEmptyValue = param.AllowEmptyValue.Value p.AllowEmptyValue = param.AllowEmptyValue.Value
p.Style = param.Style.Value p.Style = param.Style.Value
if !param.Explode.IsEmpty() { if !param.Explode.IsEmpty() {
p.Explode = &param.Explode.Value p.Explode = &param.Explode.Value
} }
p.AllowReserved = param.AllowReserved.Value p.AllowReserved = param.AllowReserved.Value
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 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)
p.Extensions = high.ExtractExtensions(param.Extensions) p.Extensions = high.ExtractExtensions(param.Extensions)
return p return p
} }
// GoLow returns the low-level Parameter used to create the high-level one. // GoLow returns the low-level Parameter used to create the high-level one.
func (p *Parameter) GoLow() *low.Parameter { func (p *Parameter) GoLow() *low.Parameter {
return p.low return p.low
} }
// GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type // GoLowUntyped will return the low-level Discriminator instance that was used to create the high-level one, with no type
func (p *Parameter) GoLowUntyped() any { func (p *Parameter) GoLowUntyped() any {
return p.low return p.low
} }
// Render will return a YAML representation of the Encoding object as a byte slice. // Render will return a YAML representation of the Encoding object as a byte slice.
func (p *Parameter) Render() ([]byte, error) { func (p *Parameter) Render() ([]byte, error) {
return yaml.Marshal(p) return yaml.Marshal(p)
} }
// MarshalYAML will create a ready to render YAML representation of the Encoding object. // MarshalYAML will create a ready to render YAML representation of the Encoding object.
func (p *Parameter) MarshalYAML() (interface{}, error) { func (p *Parameter) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(p, p.low) nb := high.NewNodeBuilder(p, p.low)
return nb.Render(), nil return nb.Render(), nil
} }
// IsExploded will return true if the parameter is exploded, false otherwise. // IsExploded will return true if the parameter is exploded, false otherwise.
func (p *Parameter) IsExploded() bool { func (p *Parameter) IsExploded() bool {
if p.Explode == nil { if p.Explode == nil {
return false return false
} }
return *p.Explode return *p.Explode
} }
// IsDefaultFormEncoding will return true if the paramter has no exploded value, or has exploded set to true, and no style // IsDefaultFormEncoding will return true if the parameter has no exploded value, or has exploded set to true, and no style
// or a style set to form. This combination is the default encoding/serialization style for parameters for OpenAPI 3+ // or a style set to form. This combination is the default encoding/serialization style for parameters for OpenAPI 3+
func (p *Parameter) IsDefaultFormEncoding() bool { func (p *Parameter) IsDefaultFormEncoding() bool {
if p.Explode == nil && (p.Style == "" || p.Style == "form") { if p.Explode == nil && (p.Style == "" || p.Style == "form") {
return true return true
} }
if p.Explode != nil && *p.Explode && (p.Style == "" || p.Style == "form") { if p.Explode != nil && *p.Explode && (p.Style == "" || p.Style == "form") {
return true return true
} }
return false return false
} }
// IsDefaultHeaderEncoding will return true if the paramter has no exploded value, or has exploded set to false, and no style // IsDefaultHeaderEncoding will return true if the parameter has no exploded value, or has exploded set to false, and no style
// or a style set to simple. This combination is the default encoding/serialization style for header parameters for OpenAPI 3+ // or a style set to simple. This combination is the default encoding/serialization style for header parameters for OpenAPI 3+
func (p *Parameter) IsDefaultHeaderEncoding() bool { func (p *Parameter) IsDefaultHeaderEncoding() bool {
if p.Explode == nil && (p.Style == "" || p.Style == "simple") { if p.Explode == nil && (p.Style == "" || p.Style == "simple") {
return true return true
} }
if p.Explode != nil && !*p.Explode && (p.Style == "" || p.Style == "simple") { if p.Explode != nil && !*p.Explode && (p.Style == "" || p.Style == "simple") {
return true return true
} }
return false return false
} }
// IsDefaultPathEncoding will return true if the paramter has no exploded value, or has exploded set to false, and no style // IsDefaultPathEncoding will return true if the parameter has no exploded value, or has exploded set to false, and no style
// or a style set to simple. This combination is the default encoding/serialization style for path parameters for OpenAPI 3+ // or a style set to simple. This combination is the default encoding/serialization style for path parameters for OpenAPI 3+
func (p *Parameter) IsDefaultPathEncoding() bool { func (p *Parameter) IsDefaultPathEncoding() bool {
return p.IsDefaultHeaderEncoding() // header default encoding and path default encoding are the same return p.IsDefaultHeaderEncoding() // header default encoding and path default encoding are the same
} }

View File

@@ -43,3 +43,88 @@ x-burgers: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }
func TestParameter_IsExploded(t *testing.T) {
explode := true
param := Parameter{
Explode: &explode,
}
assert.True(t, param.IsExploded())
explode = false
param = Parameter{
Explode: &explode,
}
assert.False(t, param.IsExploded())
param = Parameter{}
assert.False(t, param.IsExploded())
}
func TestParameter_IsDefaultFormEncoding(t *testing.T) {
param := Parameter{}
assert.True(t, param.IsDefaultFormEncoding())
param = Parameter{Style: "form"}
assert.True(t, param.IsDefaultFormEncoding())
explode := false
param = Parameter{
Explode: &explode,
}
assert.False(t, param.IsDefaultFormEncoding())
explode = true
param = Parameter{
Explode: &explode,
}
assert.True(t, param.IsDefaultFormEncoding())
param = Parameter{
Explode: &explode,
Style: "simple",
}
assert.False(t, param.IsDefaultFormEncoding())
}
func TestParameter_IsDefaultHeaderEncoding(t *testing.T) {
param := Parameter{}
assert.True(t, param.IsDefaultHeaderEncoding())
param = Parameter{Style: "simple"}
assert.True(t, param.IsDefaultHeaderEncoding())
explode := false
param = Parameter{
Explode: &explode,
Style: "simple",
}
assert.True(t, param.IsDefaultHeaderEncoding())
explode = true
param = Parameter{
Explode: &explode,
Style: "simple",
}
assert.False(t, param.IsDefaultHeaderEncoding())
explode = false
param = Parameter{
Explode: &explode,
Style: "form",
}
assert.False(t, param.IsDefaultHeaderEncoding())
}
func TestParameter_IsDefaultPathEncoding(t *testing.T) {
param := Parameter{}
assert.True(t, param.IsDefaultPathEncoding())
}

View File

@@ -4,12 +4,12 @@
package v3 package v3
import ( import (
"fmt" "fmt"
"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/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
) )
// Responses represents a high-level OpenAPI 3+ Responses object that is backed by a low-level one. // Responses represents a high-level OpenAPI 3+ Responses object that is backed by a low-level one.
@@ -25,130 +25,125 @@ import (
// //
// The Responses Object MUST contain at least one response code, and if only one response code is provided it SHOULD // The Responses Object MUST contain at least one response code, and if only one response code is provided it SHOULD
// 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 map[string]*Response `json:"-" yaml:"-"` Codes 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 map[string]any `json:"-" yaml:"-"`
low *low.Responses low *low.Responses
} }
// NewResponses will create a new high-level Responses instance from a low-level one. It operates asynchronously // NewResponses will create a new high-level Responses instance from a low-level one. It operates asynchronously
// internally, as each response may be considerable in complexity. // internally, as each response may be considerable in complexity.
func NewResponses(responses *low.Responses) *Responses { func NewResponses(responses *low.Responses) *Responses {
r := new(Responses) r := new(Responses)
r.low = responses r.low = responses
r.Extensions = high.ExtractExtensions(responses.Extensions) r.Extensions = high.ExtractExtensions(responses.Extensions)
if !responses.Default.IsEmpty() { if !responses.Default.IsEmpty() {
r.Default = NewResponse(responses.Default.Value) r.Default = NewResponse(responses.Default.Value)
} }
codes := make(map[string]*Response) codes := make(map[string]*Response)
// struct to hold response and code sent over chan. // struct to hold response and code sent over chan.
type respRes struct { type respRes struct {
code string code string
resp *Response resp *Response
} }
// build each response async for speed // build each response async for speed
rChan := make(chan respRes) rChan := make(chan respRes)
var buildResponse = func(code string, resp *low.Response, c chan respRes) { var buildResponse = func(code string, resp *low.Response, c chan respRes) {
c <- respRes{code: code, resp: NewResponse(resp)} c <- respRes{code: code, resp: NewResponse(resp)}
} }
for k, v := range responses.Codes { for k, v := range responses.Codes {
go buildResponse(k.Value, v.Value, rChan) go buildResponse(k.Value, v.Value, rChan)
} }
totalCodes := len(responses.Codes) totalCodes := len(responses.Codes)
codesParsed := 0 codesParsed := 0
for codesParsed < totalCodes { for codesParsed < totalCodes {
select { select {
case re := <-rChan: case re := <-rChan:
codesParsed++ codesParsed++
codes[re.code] = re.resp codes[re.code] = re.resp
} }
} }
r.Codes = codes r.Codes = codes
return r return r
} }
// FindResponseByCode is a shortcut for looking up code by an integer vs. a string // FindResponseByCode is a shortcut for looking up code by an integer vs. a string
func (r *Responses) FindResponseByCode(code int) *Response { func (r *Responses) FindResponseByCode(code int) *Response {
return r.Codes[fmt.Sprintf("%d", code)] return r.Codes[fmt.Sprintf("%d", code)]
}
// GetDefaultResponse will
func (r *Responses) GetDefaultResponse(code int) *Response {
return r.Codes[fmt.Sprintf("%d", code)]
} }
// GoLow returns the low-level Response object used to create the high-level one. // GoLow returns the low-level Response object used to create the high-level one.
func (r *Responses) GoLow() *low.Responses { func (r *Responses) GoLow() *low.Responses {
return r.low return r.low
} }
// GoLowUntyped will return the low-level Responses instance that was used to create the high-level one, with no type // GoLowUntyped will return the low-level Responses instance that was used to create the high-level one, with no type
func (r *Responses) GoLowUntyped() any { func (r *Responses) GoLowUntyped() any {
return r.low return r.low
} }
// Render will return a YAML representation of the Responses object as a byte slice. // Render will return a YAML representation of the Responses object as a byte slice.
func (r *Responses) Render() ([]byte, error) { func (r *Responses) Render() ([]byte, error) {
return yaml.Marshal(r) return yaml.Marshal(r)
} }
// MarshalYAML will create a ready to render YAML representation of the Responses object. // MarshalYAML will create a ready to render YAML representation of the Responses object.
func (r *Responses) MarshalYAML() (interface{}, error) { func (r *Responses) MarshalYAML() (interface{}, error) {
// map keys correctly. // map keys correctly.
m := utils.CreateEmptyMapNode() m := utils.CreateEmptyMapNode()
type responseItem struct { type responseItem struct {
resp *Response resp *Response
code string code string
line int line int
ext *yaml.Node ext *yaml.Node
} }
var mapped []*responseItem var mapped []*responseItem
for k, re := range r.Codes { for k, re := range r.Codes {
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.
if r.low != nil { if r.low != nil {
for lKey := range r.low.Codes { for lKey := range r.low.Codes {
if lKey.Value == k { if lKey.Value == k {
ln = lKey.KeyNode.Line ln = lKey.KeyNode.Line
} }
} }
} }
mapped = append(mapped, &responseItem{re, k, ln, nil}) mapped = append(mapped, &responseItem{re, k, ln, nil})
} }
// extract extensions // extract extensions
nb := high.NewNodeBuilder(r, r.low) nb := high.NewNodeBuilder(r, r.low)
extNode := nb.Render() extNode := nb.Render()
if extNode != nil && extNode.Content != nil { if extNode != nil && extNode.Content != nil {
var label string var label string
for u := range extNode.Content { for u := range extNode.Content {
if u%2 == 0 { if u%2 == 0 {
label = extNode.Content[u].Value label = extNode.Content[u].Value
continue continue
} }
mapped = append(mapped, &responseItem{nil, label, mapped = append(mapped, &responseItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]}) extNode.Content[u].Line, 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 j := range mapped {
if mapped[j].resp != nil { if mapped[j].resp != nil {
rendered, _ := mapped[j].resp.MarshalYAML() rendered, _ := mapped[j].resp.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code)) m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, rendered.(*yaml.Node)) m.Content = append(m.Content, rendered.(*yaml.Node))
} }
if mapped[j].ext != nil { if mapped[j].ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code)) m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, mapped[j].ext) m.Content = append(m.Content, mapped[j].ext)
} }
} }
return m, nil return m, nil
} }

View File

@@ -10,7 +10,7 @@ import (
) )
// Server represents a high-level OpenAPI 3+ Server object, that is backed by a low level one. // Server represents a high-level OpenAPI 3+ Server object, that is backed by a low level one.
// - https://spec.openapis.org/oas/v3.1.0#server-object // - https://spec.openapis.org/oas/v3.1.0#server-object
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"`
@@ -53,4 +53,4 @@ func (s *Server) Render() ([]byte, error) {
func (s *Server) MarshalYAML() (interface{}, error) { func (s *Server) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -55,4 +55,4 @@ func (s *ServerVariable) Render() ([]byte, error) {
func (s *ServerVariable) MarshalYAML() (interface{}, error) { func (s *ServerVariable) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -73,6 +73,7 @@ func TestLicense_Build(t *testing.T) {
func TestInfo_Hash(t *testing.T) { func TestInfo_Hash(t *testing.T) {
left := `title: princess b33f left := `title: princess b33f
summary: a thing
description: a thing description: a thing
termsOfService: https://pb33f.io termsOfService: https://pb33f.io
x-princess: b33f x-princess: b33f
@@ -85,6 +86,7 @@ version: 1.2.3
x-b33f: princess` x-b33f: princess`
right := `title: princess b33f right := `title: princess b33f
summary: a thing
description: a thing description: a thing
termsOfService: https://pb33f.io termsOfService: https://pb33f.io
x-princess: b33f x-princess: b33f

View File

@@ -33,4 +33,4 @@ func CompareOpenAPIDocuments(original, updated *v3.Document) *model.DocumentChan
// or removed and which of those changes were breaking. // or removed and which of those changes were breaking.
func CompareSwaggerDocuments(original, updated *v2.Swagger) *model.DocumentChanges { func CompareSwaggerDocuments(original, updated *v2.Swagger) *model.DocumentChanges {
return model.CompareDocuments(original, updated) return model.CompareDocuments(original, updated)
} }