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
// 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.
// 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 {
return &DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
}
return &DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
}
}
func NewClosedDocumentConfiguration() *DocumentConfiguration {
return &DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
}
}
return &DocumentConfiguration{
AllowFileReferences: 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
// if/else on the value to determine which one is set.
type DynamicValue[A any, B any] struct {
N int // 0 == A, 1 == B
A A
B B
N int // 0 == A, 1 == B
A A
B B
inline bool
}
// 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) {
d.inline = false
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.
func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
// 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)
switch to.Kind() {
case reflect.Ptr:
if r, ok := value.(high.Renderable); ok {
return r.MarshalYAML()
if d.inline {
if r, ok := value.(high.RenderableInline); ok {
return r.MarshalYAMLInline()
} else {
_ = n.Encode(value)
}
} else {
_ = n.Encode(value)
if r, ok := value.(high.Renderable); ok {
return r.MarshalYAML()
} else {
_ = n.Encode(value)
}
}
case reflect.Bool:
_ = n.Encode(value.(bool))
@@ -77,42 +93,9 @@ func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
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) {
// this is a custom renderer, we can't use the NodeBuilder out of the gate.
var n yaml.Node
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
d.inline = true
return d.MarshalYAML()
}

View File

@@ -4,71 +4,172 @@
package base
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
)
func TestDynamicValue_Render_A(t *testing.T) {
dv := &DynamicValue[string, int]{N: 0, A: "hello"}
dvb, _ := dv.Render()
assert.Equal(t, "hello", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, int]{N: 0, A: "hello"}
dvb, _ := dv.Render()
assert.Equal(t, "hello", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_B(t *testing.T) {
dv := &DynamicValue[string, int]{N: 1, B: 12345}
dvb, _ := dv.Render()
assert.Equal(t, "12345", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, int]{N: 1, B: 12345}
dvb, _ := dv.Render()
assert.Equal(t, "12345", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_Bool(t *testing.T) {
dv := &DynamicValue[string, bool]{N: 1, B: true}
dvb, _ := dv.Render()
assert.Equal(t, "true", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, bool]{N: 1, B: true}
dvb, _ := dv.Render()
assert.Equal(t, "true", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_Int64(t *testing.T) {
dv := &DynamicValue[string, int64]{N: 1, B: 12345567810}
dvb, _ := dv.Render()
assert.Equal(t, "12345567810", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, int64]{N: 1, B: 12345567810}
dvb, _ := dv.Render()
assert.Equal(t, "12345567810", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_Int32(t *testing.T) {
dv := &DynamicValue[string, int32]{N: 1, B: 1234567891}
dvb, _ := dv.Render()
assert.Equal(t, "1234567891", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, int32]{N: 1, B: 1234567891}
dvb, _ := dv.Render()
assert.Equal(t, "1234567891", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_Float32(t *testing.T) {
dv := &DynamicValue[string, float32]{N: 1, B: 23456.123}
dvb, _ := dv.Render()
assert.Equal(t, "23456.123", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, float32]{N: 1, B: 23456.123}
dvb, _ := dv.Render()
assert.Equal(t, "23456.123", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_Float64(t *testing.T) {
dv := &DynamicValue[string, float64]{N: 1, B: 23456.1233456778}
dvb, _ := dv.Render()
assert.Equal(t, "23456.1233456778", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, float64]{N: 1, B: 23456.1233456778}
dvb, _ := dv.Render()
assert.Equal(t, "23456.1233456778", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_Ptr(t *testing.T) {
type cake struct {
Cake string
}
type cake struct {
Cake string
}
dv := &DynamicValue[string, *cake]{N: 1, B: &cake{Cake: "vanilla"}}
dvb, _ := dv.Render()
assert.Equal(t, "cake: vanilla", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, *cake]{N: 1, B: &cake{Cake: "vanilla"}}
dvb, _ := dv.Render()
assert.Equal(t, "cake: vanilla", strings.TrimSpace(string(dvb)))
}
func TestDynamicValue_Render_PtrRenderable(t *testing.T) {
tag := &Tag{
Name: "cake",
}
tag := &Tag{
Name: "cake",
}
dv := &DynamicValue[string, *Tag]{N: 1, B: tag}
dvb, _ := dv.Render()
assert.Equal(t, "name: cake", strings.TrimSpace(string(dvb)))
dv := &DynamicValue[string, *Tag]{N: 1, B: tag}
dvb, _ := dv.Render()
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 (
"fmt"
"gopkg.in/yaml.v3"
"sync"
"gopkg.in/yaml.v3"
"sync"
"github.com/pb33f/libopenapi/datamodel/high"
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
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"`
Properties map[string]*SchemaProxy `json:"properties,omitempty" yaml:"properties,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
@@ -97,42 +98,10 @@ type Schema struct {
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,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"`
Extensions map[string]any `json:"-" yaml:"-"`
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.
ParentProxy *SchemaProxy `json:"-" yaml:"-"`
}
@@ -311,9 +280,11 @@ func NewSchema(schema *base.Schema) *Schema {
s.Required = req
var enum []any
if !schema.Anchor.IsEmpty() {
s.Anchor = schema.Anchor.Value
}
if !schema.Anchor.IsEmpty() {
s.Anchor = schema.Anchor.Value
}
// TODO: check this behavior.
for i := range schema.Enum.Value {
enum = append(enum, fmt.Sprint(schema.Enum.Value[i].Value))
}

View File

@@ -4,11 +4,11 @@
package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
// 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
// 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?
// - A: It's slow, takes up memory and still has runaway potential in very, very long chains.
// - 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.
//
// 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
// when a schema is needed, so the rest of the document is parsed and ready to use.
type SchemaProxy struct {
schema *low.NodeReference[*base.SchemaProxy]
buildError error
rendered *Schema
refStr string
schema *low.NodeReference[*base.SchemaProxy]
buildError error
rendered *Schema
refStr string
}
// NewSchemaProxy creates a new high-level SchemaProxy from a low-level one.
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
// as if the SchemaProxy is pre-rendered.
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
// building out new models from scratch that require a reference rather than a schema implementation.
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.
// If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access
// to that building error.
func (sp *SchemaProxy) Schema() *Schema {
if sp.rendered == nil {
s := sp.schema.Value.Schema()
if s == nil {
sp.buildError = sp.schema.Value.GetBuildError()
return nil
}
sch := NewSchema(s)
sch.ParentProxy = sp
sp.rendered = sch
return sch
} else {
return sp.rendered
}
if sp.rendered == nil {
s := sp.schema.Value.Schema()
if s == nil {
sp.buildError = sp.schema.Value.GetBuildError()
return nil
}
sch := NewSchema(s)
sch.ParentProxy = sp
sp.rendered = sch
return sch
} else {
return sp.rendered
}
}
// IsReference returns true if the SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) IsReference() bool {
if sp.refStr != "" {
return true
}
if sp.schema != nil {
return sp.schema.Value.IsSchemaReference()
}
return false
if sp.refStr != "" {
return true
}
if sp.schema != nil {
return sp.schema.Value.IsSchemaReference()
}
return false
}
// GetReference returns the location of the $ref if this SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) GetReference() string {
if sp.refStr != "" {
return sp.refStr
}
return sp.schema.Value.GetSchemaReference()
if sp.refStr != "" {
return sp.refStr
}
return sp.schema.Value.GetSchemaReference()
}
// BuildSchema operates the same way as Schema, except it will return any error along with the *Schema
func (sp *SchemaProxy) BuildSchema() (*Schema, error) {
if sp.rendered != nil {
return sp.rendered, sp.buildError
}
schema := sp.Schema()
er := sp.buildError
return schema, er
if sp.rendered != nil {
return sp.rendered, sp.buildError
}
schema := sp.Schema()
er := sp.buildError
return schema, er
}
// GetBuildError returns any error that was thrown when calling Schema()
func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError
return sp.buildError
}
func (sp *SchemaProxy) GoLow() *base.SchemaProxy {
if sp.schema == nil {
return nil
}
return sp.schema.Value
if sp.schema == nil {
return nil
}
return sp.schema.Value
}
func (sp *SchemaProxy) GoLowUntyped() any {
if sp.schema == nil {
return nil
}
return sp.schema.Value
if sp.schema == nil {
return nil
}
return sp.schema.Value
}
// Render will return a YAML representation of the Schema object as a byte slice.
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.
func (sp *SchemaProxy) MarshalYAML() (interface{}, error) {
var s *Schema
var err error
// if this schema isn't a reference, then build it out.
if !sp.IsReference() {
s, err = sp.BuildSchema()
if err != nil {
return nil, err
}
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
} else {
// do not build out a reference, just marshal the reference.
mp := utils.CreateEmptyMapNode()
mp.Content = append(mp.Content,
utils.CreateStringNode("$ref"),
utils.CreateStringNode(sp.GetReference()))
return mp, nil
}
var s *Schema
var err error
// if this schema isn't a reference, then build it out.
if !sp.IsReference() {
s, err = sp.BuildSchema()
if err != nil {
return nil, err
}
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
} else {
// do not build out a reference, just marshal the reference.
mp := utils.CreateEmptyMapNode()
mp.Content = append(mp.Content,
utils.CreateStringNode("$ref"),
utils.CreateStringNode(sp.GetReference()))
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.
func (sp *SchemaProxy) MarshalYAMLInline() (interface{}, error) {
var s *Schema
var err error
s, err = sp.BuildSchema()
if err != nil {
return nil, err
}
nb := high.NewNodeBuilder(s, s.low)
nb.Resolve = true
return nb.Render(), nil
var s *Schema
var err error
s, err = sp.BuildSchema()
if err != nil {
return nil, err
}
nb := high.NewNodeBuilder(s, s.low)
nb.Resolve = true
return nb.Render(), nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,59 +4,59 @@
package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3"
)
// 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
// tag defined in the Operation Object instances.
// - v2: https://swagger.io/specification/v2/#tagObject
// - v3: https://swagger.io/specification/#tag-object
// - v2: https://swagger.io/specification/v2/#tagObject
// - v3: https://swagger.io/specification/#tag-object
type Tag struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Extensions map[string]any
low *low.Tag
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Extensions map[string]any
low *low.Tag
}
// NewTag creates a new high-level Tag instance that is backed by a low-level one.
func NewTag(tag *low.Tag) *Tag {
t := new(Tag)
t.low = tag
if !tag.Name.IsEmpty() {
t.Name = tag.Name.Value
}
if !tag.Description.IsEmpty() {
t.Description = tag.Description.Value
}
if !tag.ExternalDocs.IsEmpty() {
t.ExternalDocs = NewExternalDoc(tag.ExternalDocs.Value)
}
t.Extensions = high.ExtractExtensions(tag.Extensions)
return t
t := new(Tag)
t.low = tag
if !tag.Name.IsEmpty() {
t.Name = tag.Name.Value
}
if !tag.Description.IsEmpty() {
t.Description = tag.Description.Value
}
if !tag.ExternalDocs.IsEmpty() {
t.ExternalDocs = NewExternalDoc(tag.ExternalDocs.Value)
}
t.Extensions = high.ExtractExtensions(tag.Extensions)
return t
}
// GoLow returns the low-level Tag instance used to create the high-level one.
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
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.
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.
func (t *Tag) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(t, t.low)
return nb.Render(), nil
}
nb := high.NewNodeBuilder(t, t.low)
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
// 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 {
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,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) {
nb := high.NewNodeBuilder(l, l.low)
return nb.Render(), nil
}
}

View File

@@ -4,114 +4,114 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
)
// 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.
// - https://spec.openapis.org/oas/v3.1.0#parameter-object
// - https://spec.openapis.org/oas/v3.1.0#parameter-object
type Parameter struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"`
Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Parameter
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"`
Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Parameter
}
// NewParameter will create a new high-level instance of a Parameter, using a low-level one.
func NewParameter(param *low.Parameter) *Parameter {
p := new(Parameter)
p.low = param
p.Name = param.Name.Value
p.In = param.In.Value
p.Description = param.Description.Value
p.Deprecated = param.Deprecated.Value
p.AllowEmptyValue = param.AllowEmptyValue.Value
p.Style = param.Style.Value
if !param.Explode.IsEmpty() {
p.Explode = &param.Explode.Value
}
p.AllowReserved = param.AllowReserved.Value
if !param.Schema.IsEmpty() {
p.Schema = base.NewSchemaProxy(&param.Schema)
}
p.Required = param.Required.Value
p.Example = param.Example.Value
p.Examples = base.ExtractExamples(param.Examples.Value)
p.Content = ExtractContent(param.Content.Value)
p.Extensions = high.ExtractExtensions(param.Extensions)
return p
p := new(Parameter)
p.low = param
p.Name = param.Name.Value
p.In = param.In.Value
p.Description = param.Description.Value
p.Deprecated = param.Deprecated.Value
p.AllowEmptyValue = param.AllowEmptyValue.Value
p.Style = param.Style.Value
if !param.Explode.IsEmpty() {
p.Explode = &param.Explode.Value
}
p.AllowReserved = param.AllowReserved.Value
if !param.Schema.IsEmpty() {
p.Schema = base.NewSchemaProxy(&param.Schema)
}
p.Required = param.Required.Value
p.Example = param.Example.Value
p.Examples = base.ExtractExamples(param.Examples.Value)
p.Content = ExtractContent(param.Content.Value)
p.Extensions = high.ExtractExtensions(param.Extensions)
return p
}
// GoLow returns the low-level Parameter used to create the high-level one.
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
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.
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.
func (p *Parameter) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(p, p.low)
return nb.Render(), nil
nb := high.NewNodeBuilder(p, p.low)
return nb.Render(), nil
}
// IsExploded will return true if the parameter is exploded, false otherwise.
func (p *Parameter) IsExploded() bool {
if p.Explode == nil {
return false
}
return *p.Explode
if p.Explode == nil {
return false
}
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+
func (p *Parameter) IsDefaultFormEncoding() bool {
if p.Explode == nil && (p.Style == "" || p.Style == "form") {
return true
}
if p.Explode != nil && *p.Explode && (p.Style == "" || p.Style == "form") {
return true
}
return false
if p.Explode == nil && (p.Style == "" || p.Style == "form") {
return true
}
if p.Explode != nil && *p.Explode && (p.Style == "" || p.Style == "form") {
return true
}
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+
func (p *Parameter) IsDefaultHeaderEncoding() bool {
if p.Explode == nil && (p.Style == "" || p.Style == "simple") {
return true
}
if p.Explode != nil && !*p.Explode && (p.Style == "" || p.Style == "simple") {
return true
}
return false
if p.Explode == nil && (p.Style == "" || p.Style == "simple") {
return true
}
if p.Explode != nil && !*p.Explode && (p.Style == "" || p.Style == "simple") {
return true
}
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+
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)))
}
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
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"fmt"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
)
// 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
// 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 {
Codes map[string]*Response `json:"-" yaml:"-"`
Default *Response `json:"default,omitempty" yaml:"default,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Responses
Codes map[string]*Response `json:"-" yaml:"-"`
Default *Response `json:"default,omitempty" yaml:"default,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Responses
}
// 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.
func NewResponses(responses *low.Responses) *Responses {
r := new(Responses)
r.low = responses
r.Extensions = high.ExtractExtensions(responses.Extensions)
if !responses.Default.IsEmpty() {
r.Default = NewResponse(responses.Default.Value)
}
codes := make(map[string]*Response)
r := new(Responses)
r.low = responses
r.Extensions = high.ExtractExtensions(responses.Extensions)
if !responses.Default.IsEmpty() {
r.Default = NewResponse(responses.Default.Value)
}
codes := make(map[string]*Response)
// struct to hold response and code sent over chan.
type respRes struct {
code string
resp *Response
}
// struct to hold response and code sent over chan.
type respRes struct {
code string
resp *Response
}
// build each response async for speed
rChan := make(chan respRes)
var buildResponse = func(code string, resp *low.Response, c chan respRes) {
c <- respRes{code: code, resp: NewResponse(resp)}
}
for k, v := range responses.Codes {
go buildResponse(k.Value, v.Value, rChan)
}
totalCodes := len(responses.Codes)
codesParsed := 0
for codesParsed < totalCodes {
select {
case re := <-rChan:
codesParsed++
codes[re.code] = re.resp
}
}
r.Codes = codes
return r
// build each response async for speed
rChan := make(chan respRes)
var buildResponse = func(code string, resp *low.Response, c chan respRes) {
c <- respRes{code: code, resp: NewResponse(resp)}
}
for k, v := range responses.Codes {
go buildResponse(k.Value, v.Value, rChan)
}
totalCodes := len(responses.Codes)
codesParsed := 0
for codesParsed < totalCodes {
select {
case re := <-rChan:
codesParsed++
codes[re.code] = re.resp
}
}
r.Codes = codes
return r
}
// FindResponseByCode is a shortcut for looking up code by an integer vs. a string
func (r *Responses) FindResponseByCode(code int) *Response {
return r.Codes[fmt.Sprintf("%d", code)]
}
// GetDefaultResponse will
func (r *Responses) GetDefaultResponse(code int) *Response {
return r.Codes[fmt.Sprintf("%d", code)]
return r.Codes[fmt.Sprintf("%d", code)]
}
// GoLow returns the low-level Response object used to create the high-level one.
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
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.
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.
func (r *Responses) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := utils.CreateEmptyMapNode()
type responseItem struct {
resp *Response
code string
line int
ext *yaml.Node
}
var mapped []*responseItem
// map keys correctly.
m := utils.CreateEmptyMapNode()
type responseItem struct {
resp *Response
code string
line int
ext *yaml.Node
}
var mapped []*responseItem
for k, re := range r.Codes {
ln := 9999 // default to a high value to weight new content to the bottom.
if r.low != nil {
for lKey := range r.low.Codes {
if lKey.Value == k {
ln = lKey.KeyNode.Line
}
}
}
mapped = append(mapped, &responseItem{re, k, ln, nil})
}
for k, re := range r.Codes {
ln := 9999 // default to a high value to weight new content to the bottom.
if r.low != nil {
for lKey := range r.low.Codes {
if lKey.Value == k {
ln = lKey.KeyNode.Line
}
}
}
mapped = append(mapped, &responseItem{re, k, ln, nil})
}
// extract extensions
nb := high.NewNodeBuilder(r, r.low)
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, &responseItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
}
}
// extract extensions
nb := high.NewNodeBuilder(r, r.low)
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, &responseItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
}
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].resp != nil {
rendered, _ := mapped[j].resp.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, mapped[j].ext)
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].resp != nil {
rendered, _ := mapped[j].resp.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
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.
// - https://spec.openapis.org/oas/v3.1.0#server-object
// - https://spec.openapis.org/oas/v3.1.0#server-object
type Server struct {
URL string `json:"url,omitempty" yaml:"url,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) {
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
}
}

View File

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

View File

@@ -73,6 +73,7 @@ func TestLicense_Build(t *testing.T) {
func TestInfo_Hash(t *testing.T) {
left := `title: princess b33f
summary: a thing
description: a thing
termsOfService: https://pb33f.io
x-princess: b33f
@@ -85,6 +86,7 @@ version: 1.2.3
x-b33f: princess`
right := `title: princess b33f
summary: a thing
description: a thing
termsOfService: https://pb33f.io
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.
func CompareSwaggerDocuments(original, updated *v2.Swagger) *model.DocumentChanges {
return model.CompareDocuments(original, updated)
}
}