more mutable objects, more tightening up

This commit is contained in:
Dave Shanley
2023-03-10 16:47:55 -05:00
parent 6a8c890056
commit 000eada3f4
17 changed files with 479 additions and 226 deletions

View File

@@ -45,9 +45,6 @@ func (e *Example) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the Example object.
func (e *Example) MarshalYAML() (interface{}, error) {
if e == nil {
return nil, nil
}
nb := high.NewNodeBuilder(e, e.low)
return nb.Render(), nil
}

View File

@@ -922,161 +922,3 @@ func TestNewSchemaProxy_RenderSchemaCheckAdditionalPropertiesSlice(t *testing.T)
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes)))
}
/*
type: object
description: something object
discriminator:
propertyName: athing
mapping:
log: cat
pizza: party
allOf:
- type: object
description: an allof thing
properties:
allOfA:
type: string
description: allOfA description
example: 'allOfAExp'
allOfB:
type: string
description: allOfB description
example: 'allOfBExp'
oneOf:
type: object
description: a oneof thing
properties:
oneOfA:
type: string
description: oneOfA description
example: 'oneOfAExp'
oneOfB:
type: string
description: oneOfB description
example: 'oneOfBExp'
anyOf:
type: object
description: an anyOf thing
properties:
anyOfA:
type: string
description: anyOfA description
example: 'anyOfAExp'
anyOfB:
type: string
description: anyOfB description
example: 'anyOfBExp'
not:
type: object
description: a not thing
properties:
notA:
type: string
description: notA description
example: 'notAExp'
notB:
type: string
description: notB description
example: 'notBExp'
items:
type: object
description: an items thing
properties:
itemsA:
type: string
description: itemsA description
example: 'itemsAExp'
itemsB:
type: string
description: itemsB description
example: 'itemsBExp'
prefixItems:
type: object
description: an items thing
properties:
itemsA:
type: string
description: itemsA description
example: 'itemsAExp'
itemsB:
type: string
description: itemsB description
example: 'itemsBExp'
properties:
somethingBee:
type: number
somethingThree:
type: number
somethingTwo:
type: number
somethingOne:
type: number
somethingA:
type: number
description: a number
example: 2
somethingB:
type: object
description: an object
externalDocs:
description: the best docs
url: https://pb33f.io
properties:
somethingBProp:
type: string
description: something b subprop
example: picnics are nice.
xml:
name: an xml thing
namespace: an xml namespace
prefix: a prefix
attribute: true
wrapped: false
x-pizza: love
additionalProperties:
why: yes
thatIs: true
additionalProperties: true
xml:
name: XML Thing
externalDocs:
url: https://pb33f.io/docs
enum: [fish, cake]
required: [cake, fish]
maxLength: 10
minLength: 1
maxItems: 10
minItems: 1
maxProperties: 10
minProperties: 1
nullable: true
readOnly: true
writeOnly: false
deprecated: true
contains:
type: int
minContains: 1
maxContains: 10
if:
type: string
else:
type: integer
then:
type: boolean
dependentSchemas:
schemaOne:
type: string
patternProperties:
patternOne:
type: string
propertyNames:
type: string
unevaluatedItems:
type: boolean
unevaluatedProperties:
type: integer
*/

View File

@@ -253,7 +253,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
// number of the tag.
var orderedCollection []*NodeEntry
m := reflect.ValueOf(value)
for _, k := range m.MapKeys() {
for g, k := range m.MapKeys() {
var x string
// extract key
@@ -275,7 +275,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
if pr, ok := gh.(low.HasValueUnTyped); ok {
fg := reflect.ValueOf(pr.GetValueUntyped())
found := false
for _, ky := range fg.MapKeys() {
for j, ky := range fg.MapKeys() {
if we, wok := ky.Interface().(low.HasKeyNode); wok {
er := we.GetKeyNode().Value
if er == x {
@@ -288,14 +288,17 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
})
}
} else {
// this is a map, without any low level details available
found = true
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999,
Value: m.MapIndex(k).Interface(),
})
uu := ky.Interface()
if uu == x {
// this is a map, without any low level details available
found = true
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: uu.(string),
Key: uu.(string),
Line: 9999 + j,
Value: fg.MapIndex(ky).Interface(),
})
}
}
}
if found != true {
@@ -303,7 +306,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
@@ -319,7 +322,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
Tag: x,
Key: x,
Line: ky.Interface().(low.HasKeyNode).GetKeyNode().Line,
Value: m.MapIndex(k).Interface(),
Value: iu.MapIndex(ky).Interface(),
})
}
}
@@ -336,7 +339,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
@@ -345,7 +348,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
@@ -357,6 +360,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
})
// create an empty map.
p := CreateEmptyMapNode()
// build out each map node in original order.
@@ -385,9 +389,13 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
case reflect.Struct:
if r, ok := value.(low.ValueReference[any]); ok {
valueNode = r.GetValueNode()
} else {
panic("not supported yet")
break
}
if r, ok := value.(low.ValueReference[string]); ok {
valueNode = r.GetValueNode()
break
}
panic("not supported yet")
case reflect.Ptr:
if r, ok := value.(Renderable); ok {

View File

@@ -3,7 +3,12 @@
package v3
import low "github.com/pb33f/libopenapi/datamodel/low/v3"
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
"sort"
)
// Callback represents a high-level Callback object for OpenAPI 3+.
//
@@ -13,8 +18,8 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v3"
// that identifies a URL to use for the callback operation.
// - https://spec.openapis.org/oas/v3.1.0#callback-object
type Callback struct {
Expression map[string]*PathItem
Extensions map[string]any
Expression map[string]*PathItem `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Callback
}
@@ -37,3 +42,46 @@ func NewCallback(lowCallback *low.Callback) *Callback {
func (c *Callback) GoLow() *low.Callback {
return c.low
}
// Render will return a YAML representation of the Callback object as a byte slice.
func (c *Callback) Render() ([]byte, error) {
return yaml.Marshal(c)
}
// MarshalYAML will create a ready to render YAML representation of the Callback object.
func (c *Callback) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := high.CreateEmptyMapNode()
type cbItem struct {
cb *PathItem
exp string
line int
}
var mapped []*cbItem
for k, ex := range c.Expression {
ln := 9999 // default to a high value to weight new content to the bottom.
if c.low != nil {
lcb := c.low.FindExpression(k)
if lcb != nil {
ln = lcb.ValueNode.Line
}
}
mapped = append(mapped, &cbItem{ex, k, ln})
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
rendered, _ := mapped[j].cb.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].exp))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
nb := high.NewNodeBuilder(c, c.low)
extNode := nb.Render()
if extNode.Content != nil {
m.Content = append(m.Content, extNode.Content...)
}
return m, nil
}

View File

@@ -0,0 +1,92 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
)
func TestCallback_MarshalYAML(t *testing.T) {
cb := &Callback{
Expression: map[string]*PathItem{
"https://pb33f.io": {
Get: &Operation{
OperationId: "oneTwoThree",
},
},
"https://pb33f.io/libopenapi": {
Get: &Operation{
OperationId: "openaypeeeye",
},
},
},
Extensions: map[string]any{
"x-burgers": "why not?",
},
}
rend, _ := cb.Render()
desired := `https://pb33f.io:
get:
operationId: oneTwoThree
https://pb33f.io/libopenapi:
get:
operationId: openaypeeeye
x-burgers: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
// mutate
cb.Expression["https://pb33f.io"].Get.OperationId = "blim-blam"
cb.Extensions = map[string]interface{}{"x-burgers": "yes please!"}
desired = `https://pb33f.io:
get:
operationId: blim-blam
https://pb33f.io/libopenapi:
get:
operationId: openaypeeeye
x-burgers: yes please!`
rend, _ = cb.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
k := `x-break-everything: please
"{$request.query.queryUrl}":
post:
requestBody: null
description: Callback payload
content:
application/json:
schema:
type: string
responses:
"200":
description: callback successfully processes
`
var idxNode yaml.Node
err := yaml.Unmarshal([]byte(k), &idxNode)
assert.NoError(t, err)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.Callback
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx)
r := NewCallback(&n)
assert.Equal(t, "please", r.Extensions["x-break-everything"])
rend, _ = r.Render()
assert.Equal(t, k, strings.TrimSpace(string(rend)))
}

View File

@@ -9,6 +9,7 @@ import (
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
)
// used for internal channel co-ordination for building out different component types.
@@ -29,16 +30,20 @@ const (
// will have no effect on the API unless they are explicitly referenced from properties outside the components object.
// - https://spec.openapis.org/oas/v3.1.0#components-object
type Components struct {
Schemas map[string]*highbase.SchemaProxy
Responses map[string]*Response
Parameters map[string]*Parameter
Examples map[string]*highbase.Example
RequestBodies map[string]*RequestBody
Headers map[string]*Header
SecuritySchemes map[string]*SecurityScheme
Links map[string]*Link
Callbacks map[string]*Callback
Extensions map[string]any
//Schemas map[string]*highbase.SchemaProxy `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Schemas map[string]*highbase.SchemaProxy `json:"-" yaml:"-"`
Responses map[string]*Response `json:"-" yaml:"-"`
//Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
Parameters map[string]*Parameter `json:"-" yaml:"-"`
//Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
//Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"`
Examples map[string]*highbase.Example `json:"-" yaml:"-"`
RequestBodies map[string]*RequestBody `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
SecuritySchemes map[string]*SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Components
}
@@ -84,7 +89,7 @@ func NewComponents(comp *low.Components) *Components {
go buildComponent[*Parameter, *low.Parameter](parameters, k.Value, v.Value, paramChan, NewParameter)
}
for k, v := range comp.Examples.Value {
go buildComponent[*highbase.Example, *base.Example](parameters, k.Value, v.Value, exampleChan, highbase.NewExample)
go buildComponent[*highbase.Example, *base.Example](examples, k.Value, v.Value, exampleChan, highbase.NewExample)
}
for k, v := range comp.RequestBodies.Value {
go buildComponent[*RequestBody, *low.RequestBody](requestBodies, k.Value, v.Value,
@@ -162,7 +167,8 @@ func buildComponent[N any, O any](comp int, key string, orig O, c chan component
}
// build out a schema
func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*base.SchemaProxy], c chan componentResult[*highbase.SchemaProxy]) {
func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*base.SchemaProxy],
c chan componentResult[*highbase.SchemaProxy]) {
var sch *highbase.SchemaProxy
sch = highbase.NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
Value: orig.Value,
@@ -175,3 +181,14 @@ func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference
func (c *Components) GoLow() *low.Components {
return c.low
}
// Render will return a YAML representation of the Components object as a byte slice.
func (c *Components) Render() ([]byte, error) {
return yaml.Marshal(c)
}
// MarshalYAML will create a ready to render YAML representation of the Response object.
func (c *Components) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(c, c.low)
return nb.Render(), nil
}

View File

@@ -0,0 +1,68 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
)
func TestComponents_MarshalYAML(t *testing.T) {
comp := &Components{
Responses: map[string]*Response{
"200": {
Description: "OK",
},
},
Parameters: map[string]*Parameter{
"id": {
Name: "id",
In: "path",
},
},
RequestBodies: map[string]*RequestBody{
"body": {
Content: map[string]*MediaType{
"application/json": {
Example: "why?",
},
},
},
},
}
dat, _ := comp.Render()
var idxNode yaml.Node
_ = yaml.Unmarshal(dat, &idxNode)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.Components
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx)
r := NewComponents(&n)
desired := `responses:
"200":
description: OK
parameters:
id:
name: id
in: path
requestBodies:
body:
content:
application/json:
example: why?`
dat, _ = r.Render()
assert.Equal(t, desired, strings.TrimSpace(string(dat)))
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// Copyright 2022-2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
// Package v3 represents all OpenAPI 3+ high-level models. High-level models are easy to navigate
@@ -30,18 +30,18 @@ type Document struct {
Info *base.Info `json:"info,omitempty" yaml:"info,omitempty"`
// Servers is a slice of Server instances which provide connectivity information to a target server. If the servers
// property is not provided, or is an empty array, the default value would be a Server Object with a url value of /.
// property is not provided, or is an empty array, the default value would be a Server Object with an url value of /.
// - https://spec.openapis.org/oas/v3.1.0#server-object
Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"`
// Paths contains all the PathItem definitions for the specification.
// The available paths and operations for the API, The most important part of ths spec.
// - https://spec.openapis.org/oas/v3.1.0#paths-object
Paths *Paths `json:"-" yaml:"-"`
Paths *Paths `json:"paths,omitempty" yaml:"paths,omitempty"`
// Components is an element to hold various schemas for the document.
// - https://spec.openapis.org/oas/v3.1.0#components-object
Components *Components `json:"-" yaml:"-"`
Components *Components `json:"components,omitempty" yaml:"components,omitempty"`
// Security contains global security requirements/roles for the specification
// A declaration of which security mechanisms can be used across the API. The list of values includes alternative
@@ -78,7 +78,7 @@ type Document struct {
// for example by an out-of-band registration. The key name is a unique string to refer to each webhook,
// while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider
// and the expected responses. An example is available.
Webhooks map[string]*PathItem `json:"-" yaml:"-"`
Webhooks map[string]*PathItem `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
// Index is a reference to the *index.SpecIndex that was created for the document and used
// as a guide when building out the Document. Ideal if further processing is required on the model and
@@ -86,7 +86,7 @@ type Document struct {
//
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
Index *index.SpecIndex `json:"-" yaml:"-"`
low *low.Document `json:"-" yaml:"-"`
low *low.Document
}
// NewDocument will create a new high-level Document from a low-level one.

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"net/url"
"os"
"testing"
"github.com/pb33f/libopenapi/datamodel"
@@ -22,7 +23,10 @@ func initTest() {
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = lowv3.CreateDocument(info)
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
})
if err != nil {
panic("broken something")
}
@@ -481,17 +485,18 @@ func TestDocument_MarshalYAML(t *testing.T) {
h := NewDocument(lowDoc)
// render the document to YAML
r, _ := h.Render()
os.WriteFile("rendered.yaml", r, 0644)
// re-parse the document
info, _ := datamodel.ExtractSpecInfo(r)
lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
assert.Nil(t, e)
highDoc := NewDocument(lDoc)
assert.Equal(t, "3.1.0", highDoc.Version)
// TODO: COMPLETE THIS
assert.Len(t, highDoc.Paths.PathItems, 1)
}

View File

@@ -5,8 +5,11 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"io/ioutil"
"strings"
"testing"
@@ -47,3 +50,31 @@ example: testing a nice mutation`
assert.Equal(t, op, strings.TrimSpace(string(yml)))
}
func TestMediaType_Examples(t *testing.T) {
yml := `examples:
pbjBurger:
summary: A horrible, nutty, sticky mess.
value:
name: Peanut And Jelly
numPatties: 3
cakeBurger:
summary: A sickly, sweet, atrocity
value:
name: Chocolate Cake Burger
numPatties: 5`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.MediaType
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx)
r := NewMediaType(&n)
rend, _ := r.Render()
assert.Equal(t, yml, strings.TrimSpace(string(rend)))
}

View File

@@ -65,16 +65,13 @@ func (p *Paths) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the Paths object.
func (p *Paths) MarshalYAML() (interface{}, error) {
if p == nil {
return nil, nil
}
// map keys correctly.
m := high.CreateEmptyMapNode()
type pathItem struct {
pi *PathItem
path string
line int
pi *PathItem
path string
line int
rendered *yaml.Node
}
var mapped []*pathItem
@@ -86,17 +83,33 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
ln = lpi.ValueNode.Line
}
}
mapped = append(mapped, &pathItem{pi, k, ln})
mapped = append(mapped, &pathItem{pi, k, ln, nil})
}
nb := high.NewNodeBuilder(p, p.low)
extNode := nb.Render()
if extNode.Content != nil {
for u := range extNode.Content {
mapped = append(mapped, &pathItem{nil, extNode.Content[u].Value,
extNode.Content[u].Line, extNode.Content[u]})
}
//m.Content = append(m.Content, extNode.Content...)
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
rendered, _ := mapped[j].pi.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, rendered.(*yaml.Node))
if mapped[j].pi != nil {
rendered, _ := mapped[j].pi.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].rendered != nil {
m.Content = append(m.Content, mapped[j].rendered)
}
}
return m, nil
}

View File

@@ -60,4 +60,3 @@ func (r *Response) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(r, r.low)
return nb.Render(), nil
}

View File

@@ -7,6 +7,8 @@ import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
"sort"
)
// Responses represents a high-level OpenAPI 3+ Responses object that is backed by a low-level one.
@@ -24,9 +26,9 @@ import (
// be the response for a successful operation call.
// - https://spec.openapis.org/oas/v3.1.0#responses-object
type Responses struct {
Codes map[string]*Response
Default *Response
Extensions map[string]any
Codes map[string]*Response `json:"-" yaml:"-"`
Default *Response `json:"default,omitempty" yaml:"default,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.Responses
}
@@ -77,3 +79,41 @@ func (r *Responses) FindResponseByCode(code int) *Response {
func (r *Responses) GoLow() *low.Responses {
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)
}
// MarshalYAML will create a ready to render YAML representation of the Responses object.
func (r *Responses) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := high.CreateEmptyMapNode()
type responseItem struct {
resp *Response
code string
line int
}
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 {
lpi := r.low.FindResponseByCode(k)
if lpi != nil {
ln = lpi.ValueNode.Line
}
}
mapped = append(mapped, &responseItem{re, k, ln})
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
rendered, _ := mapped[j].resp.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
return m, nil
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
)
@@ -36,3 +37,33 @@ func TestNewResponses(t *testing.T) {
assert.Equal(t, 1, r.GoLow().Default.KeyNode.Line)
}
func TestResponses_MarshalYAML(t *testing.T) {
yml := `"201":
description: this is a response
content:
something/thing:
example: cake
"404":
description: this is a 404
content:
something/thing:
example: why do you need an example?
"200":
description: OK! not bad.`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.Responses
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx)
r := NewResponses(&n)
rend, _ := r.Render()
assert.Equal(t, yml, strings.TrimSpace(string(rend)))
}

View File

@@ -6,6 +6,7 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
)
// SecurityScheme represents a high-level OpenAPI 3+ SecurityScheme object that is backed by a low-level one.
@@ -19,15 +20,15 @@ import (
// Recommended for most use case is Authorization Code Grant flow with PKCE.
// - https://spec.openapis.org/oas/v3.1.0#security-scheme-object
type SecurityScheme struct {
Type string
Description string
Name string
In string
Scheme string
BearerFormat string
Flows *OAuthFlows
OpenIdConnectUrl string
Extensions map[string]any
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.SecurityScheme
}
@@ -53,3 +54,14 @@ func NewSecurityScheme(ss *low.SecurityScheme) *SecurityScheme {
func (s *SecurityScheme) GoLow() *low.SecurityScheme {
return s.low
}
// Render will return a YAML representation of the SecurityScheme object as a byte slice.
func (s *SecurityScheme) Render() ([]byte, error) {
return yaml.Marshal(s)
}
// MarshalYAML will create a ready to render YAML representation of the Response object.
func (s *SecurityScheme) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
}

View File

@@ -0,0 +1,47 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
)
func TestSecurityScheme_MarshalYAML(t *testing.T) {
ss := &SecurityScheme{
Type: "apiKey",
Description: "this is a description",
Name: "superSecret",
In: "header",
Scheme: "https",
}
dat, _ := ss.Render()
var idxNode yaml.Node
_ = yaml.Unmarshal(dat, &idxNode)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.SecurityScheme
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx)
r := NewSecurityScheme(&n)
dat, _ = r.Render()
desired := `type: apiKey
description: this is a description
name: superSecret
in: header
scheme: https`
assert.Equal(t, desired, strings.TrimSpace(string(dat)))
}

View File

@@ -48,6 +48,9 @@ func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error {
currentCB = callbackNode
continue
}
if strings.HasPrefix(currentCB.Value, "x-") {
continue // ignore extension.
}
callback, eErr, isRef, rv := low.ExtractObjectRaw[*PathItem](callbackNode, idx)
if eErr != nil {
return eErr