mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-10 04:20:24 +00:00
more mutable objects, more tightening up
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
92
datamodel/high/v3/callback_test.go
Normal file
92
datamodel/high/v3/callback_test.go
Normal 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)))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
68
datamodel/high/v3/components_test.go
Normal file
68
datamodel/high/v3/components_test.go
Normal 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)))
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -60,4 +60,3 @@ func (r *Response) MarshalYAML() (interface{}, error) {
|
||||
nb := high.NewNodeBuilder(r, r.low)
|
||||
return nb.Render(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
47
datamodel/high/v3/security_scheme_test.go
Normal file
47
datamodel/high/v3/security_scheme_test.go
Normal 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)))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user