Churning through v3 models now.

mutations everywhere, for everyone!
This commit is contained in:
Dave Shanley
2023-03-07 10:34:27 -05:00
parent 4686635ce1
commit 3544ed0303
15 changed files with 431 additions and 67 deletions

View File

@@ -67,6 +67,16 @@ func (sp *SchemaProxy) Schema() *Schema {
return sch return sch
} }
// IsReference returns true if the SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) IsReference() bool {
return sp.schema.Value.IsSchemaReference()
}
// GetReference returns the location of the $ref if this SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) GetReference() string {
return sp.schema.Value.GetSchemaReference()
}
// BuildSchema operates the same way as Schema, except it will return any error along with the *Schema // BuildSchema operates the same way as Schema, except it will return any error along with the *Schema
func (sp *SchemaProxy) BuildSchema() (*Schema, error) { func (sp *SchemaProxy) BuildSchema() (*Schema, error) {
schema := sp.Schema() schema := sp.Schema()
@@ -96,12 +106,23 @@ func (sp *SchemaProxy) MarshalYAML() (interface{}, error) {
if sp == nil { if sp == nil {
return nil, nil return nil, nil
} }
s, err := sp.BuildSchema() 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 { if err != nil {
return nil, err return nil, err
} }
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil return nb.Render(), nil
} else {
// do not build out a reference, just marshal the reference.
mp := high.CreateEmptyMapNode()
mp.Content = append(mp.Content,
high.CreateStringNode("$ref"),
high.CreateStringNode(sp.GetReference()))
return mp, nil
}
} }

View File

@@ -211,6 +211,33 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
valueNode = CreateBoolNode("true") valueNode = CreateBoolNode("true")
break break
case reflect.Int:
if value != nil {
val := strconv.Itoa(value.(int))
valueNode = CreateIntNode(val)
} else {
return parent
}
break
case reflect.Int64:
if value != nil {
val := strconv.FormatInt(value.(int64), 10)
valueNode = CreateIntNode(val)
} else {
return parent
}
break
case reflect.Float64:
if value != nil {
val := strconv.FormatFloat(value.(float64), 'f', 2, 64)
valueNode = CreateFloatNode(val)
} else {
return parent
}
break
case reflect.Map: case reflect.Map:
// the keys will be rendered randomly, if we don't find out the original line // the keys will be rendered randomly, if we don't find out the original line
@@ -239,12 +266,22 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
if pr, ok := gh.(low.HasValueUnTyped); ok { if pr, ok := gh.(low.HasValueUnTyped); ok {
fg := reflect.ValueOf(pr.GetValueUntyped()) fg := reflect.ValueOf(pr.GetValueUntyped())
for _, ky := range fg.MapKeys() { for _, ky := range fg.MapKeys() {
er := ky.Interface().(low.HasKeyNode).GetKeyNode().Value if we, wok := ky.Interface().(low.HasKeyNode); wok {
er := we.GetKeyNode().Value
if er == x { if er == x {
orderedCollection = append(orderedCollection, &NodeEntry{ orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x, Tag: x,
Key: x, Key: x,
Line: ky.Interface().(low.HasKeyNode).GetKeyNode().Line, Line: we.GetKeyNode().Line,
Value: m.MapIndex(k).Interface(),
})
}
} else {
// this is a map, without any low level details available
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999,
Value: m.MapIndex(k).Interface(), Value: m.MapIndex(k).Interface(),
}) })
} }
@@ -296,7 +333,11 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any)
for _, cv := range orderedCollection { for _, cv := range orderedCollection {
n.AddYAMLNode(p, cv.Tag, cv.Key, cv.Value) n.AddYAMLNode(p, cv.Tag, cv.Key, cv.Value)
} }
if len(p.Content) > 0 {
valueNode = p valueNode = p
} else {
return parent
}
case reflect.Slice: case reflect.Slice:
if vo.IsNil() { if vo.IsNil() {
@@ -383,6 +424,24 @@ func CreateBoolNode(str string) *yaml.Node {
return n return n
} }
func CreateIntNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!int",
Value: str,
}
return n
}
func CreateFloatNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!float",
Value: str,
}
return n
}
type Renderable interface { type Renderable interface {
MarshalYAML() (interface{}, error) MarshalYAML() (interface{}, error)
} }

View File

@@ -22,26 +22,26 @@ type Document struct {
// Version is the version of OpenAPI being used, extracted from the 'openapi: x.x.x' definition. // Version is the version of OpenAPI being used, extracted from the 'openapi: x.x.x' definition.
// This is not a standard property of the OpenAPI model, it's a convenience mechanism only. // This is not a standard property of the OpenAPI model, it's a convenience mechanism only.
Version string Version string `json:"openapi,omitempty" yaml:"openapi,omitempty"`
// Info represents a specification Info definitions // Info represents a specification Info definitions
// Provides metadata about the API. The metadata MAY be used by tooling as required. // Provides metadata about the API. The metadata MAY be used by tooling as required.
// - https://spec.openapis.org/oas/v3.1.0#info-object // - https://spec.openapis.org/oas/v3.1.0#info-object
Info *base.Info 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 // 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 a url value of /.
// - https://spec.openapis.org/oas/v3.1.0#server-object // - https://spec.openapis.org/oas/v3.1.0#server-object
Servers []*Server Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"`
// Paths contains all the PathItem definitions for the specification. // Paths contains all the PathItem definitions for the specification.
// The available paths and operations for the API, The most important part of ths spec. // 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 // - https://spec.openapis.org/oas/v3.1.0#paths-object
Paths *Paths Paths *Paths `json:"-" yaml:"-"`
// Components is an element to hold various schemas for the document. // Components is an element to hold various schemas for the document.
// - https://spec.openapis.org/oas/v3.1.0#components-object // - https://spec.openapis.org/oas/v3.1.0#components-object
Components *Components Components *Components `json:"-" yaml:"-"`
// Security contains global security requirements/roles for the specification // 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 // A declaration of which security mechanisms can be used across the API. The list of values includes alternative
@@ -49,7 +49,7 @@ type Document struct {
// to authorize a request. Individual operations can override this definition. To make security optional, // to authorize a request. Individual operations can override this definition. To make security optional,
// an empty security requirement ({}) can be included in the array. // an empty security requirement ({}) can be included in the array.
// - https://spec.openapis.org/oas/v3.1.0#security-requirement-object // - https://spec.openapis.org/oas/v3.1.0#security-requirement-object
Security []*base.SecurityRequirement Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"`
// Tags is a slice of base.Tag instances defined by the specification // Tags is a slice of base.Tag instances defined by the specification
// A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on // A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on
@@ -57,20 +57,20 @@ type Document struct {
// The tags that are not declared MAY be organized randomly or based on the tools logic. // The tags that are not declared MAY be organized randomly or based on the tools logic.
// Each tag name in the list MUST be unique. // Each tag name in the list MUST be unique.
// - https://spec.openapis.org/oas/v3.1.0#tag-object // - https://spec.openapis.org/oas/v3.1.0#tag-object
Tags []*base.Tag Tags []*base.Tag `json:"tags,omitempty" yaml:"tags,omitempty"`
// ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit. // ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit.
// - https://spec.openapis.org/oas/v3.1.0#external-documentation-object // - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
ExternalDocs *base.ExternalDoc ExternalDocs *base.ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Extensions contains all custom extensions defined for the top-level document. // Extensions contains all custom extensions defined for the top-level document.
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
// JsonSchemaDialect is a 3.1+ property that sets the dialect to use for validating *base.Schema definitions // JsonSchemaDialect is a 3.1+ property that sets the dialect to use for validating *base.Schema definitions
// The default value for the $schema keyword within Schema Objects contained within this OAS document. // The default value for the $schema keyword within Schema Objects contained within this OAS document.
// This MUST be in the form of a URI. // This MUST be in the form of a URI.
// - https://spec.openapis.org/oas/v3.1.0#schema-object // - https://spec.openapis.org/oas/v3.1.0#schema-object
JsonSchemaDialect string JsonSchemaDialect string `json:"$schema,omitempty" yaml:"$schema,omitempty"`
// Webhooks is a 3.1+ property that is similar to callbacks, except, this defines incoming webhooks. // Webhooks is a 3.1+ property that is similar to callbacks, except, this defines incoming webhooks.
// The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. // The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement.
@@ -78,15 +78,15 @@ type Document struct {
// for example by an out-of-band registration. The key name is a unique string to refer to each webhook, // for example by an out-of-band registration. The key name is a unique string to refer to each webhook,
// while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider // while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider
// and the expected responses. An example is available. // and the expected responses. An example is available.
Webhooks map[string]*PathItem Webhooks map[string]*PathItem `json:"-" yaml:"-"`
// Index is a reference to the *index.SpecIndex that was created for the document and used // Index is a reference to the *index.SpecIndex that was created for the document and used
// as a guide when building out the Document. Ideal if further processing is required on the model and // as a guide when building out the Document. Ideal if further processing is required on the model and
// the original details are required to continue the work. // the original details are required to continue the work.
// //
// This property is not a part of the OpenAPI schema, this is custom to libopenapi. // This property is not a part of the OpenAPI schema, this is custom to libopenapi.
Index *index.SpecIndex Index *index.SpecIndex `json:"-" yaml:"-"`
low *low.Document low *low.Document `json:"-" yaml:"-"`
} }
// NewDocument will create a new high-level Document from a low-level one. // NewDocument will create a new high-level Document from a low-level one.
@@ -154,18 +154,9 @@ func (d *Document) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the Document object. // MarshalYAML will create a ready to render YAML representation of the Document object.
func (d *Document) MarshalYAML() (interface{}, error) { func (d *Document) MarshalYAML() (interface{}, error) {
n := high.CreateEmptyMapNode() if d == nil {
//high.AddYAMLNode(n, low.SchemaDialectLabel, d.JsonSchemaDialect) return nil, nil
//high.AddYAMLNode(n, low.OpenAPILabel, d.Version) }
//high.AddYAMLNode(n, low.InfoLabel, d.Info) nb := high.NewNodeBuilder(d, d.low)
//high.AddYAMLNode(n, low.TagsLabel, d.Tags) return nb.Render(), nil
//high.AddYAMLNode(n, low.ServersLabel, d.Servers)
//high.AddYAMLNode(n, low.SecurityLabel, d.Security)
//high.AddYAMLNode(n, low.ServersLabel, d.Servers)
//high.AddYAMLNode(n, low.ExternalDocsLabel, d.ExternalDocs)
//high.AddYAMLNode(n, low.PathsLabel, d.Paths)
//high.AddYAMLNode(n, low.ComponentsLabel, d.Components)
//high.AddYAMLNode(n, low.WebhooksLabel, d.Webhooks)
//high.MarshalExtensions(n, d.Extensions)
return n, nil
} }

View File

@@ -485,9 +485,10 @@ func TestDocument_MarshalYAML(t *testing.T) {
r, _ := h.Render() r, _ := h.Render()
info, _ := datamodel.ExtractSpecInfo(r) info, _ := datamodel.ExtractSpecInfo(r)
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration()) lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
highDoc := NewDocument(lowDoc) assert.Nil(t, e)
highDoc := NewDocument(lDoc)
assert.Equal(t, "3.1.0", highDoc.Version) assert.Equal(t, "3.1.0", highDoc.Version)
// TODO: COMPLETE THIS // TODO: COMPLETE THIS

View File

@@ -6,6 +6,7 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
) )
// Link represents a high-level OpenAPI 3+ Link object that is backed by a low-level one. // Link represents a high-level OpenAPI 3+ Link object that is backed by a low-level one.
@@ -21,13 +22,13 @@ import (
// in an operation and using them as parameters while invoking the linked operation. // in an operation and using them as parameters while invoking the linked operation.
// - https://spec.openapis.org/oas/v3.1.0#link-object // - https://spec.openapis.org/oas/v3.1.0#link-object
type Link struct { type Link struct {
OperationRef string OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationId string OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters map[string]string Parameters map[string]string `json:"parameters,omitempty" yaml:"parameters,omitempty"`
RequestBody string RequestBody string `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
Description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
Server *Server Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
low *low.Link low *low.Link
} }
@@ -55,3 +56,17 @@ func NewLink(link *low.Link) *Link {
func (l *Link) GoLow() *low.Link { func (l *Link) GoLow() *low.Link {
return l.low return l.low
} }
// Render will return a YAML representation of the Link object as a byte slice.
func (l *Link) Render() ([]byte, error) {
return yaml.Marshal(l)
}
// MarshalYAML will create a ready to render YAML representation of the Link object.
func (l *Link) MarshalYAML() (interface{}, error) {
if l == nil {
return nil, nil
}
nb := high.NewNodeBuilder(l, l.low)
return nb.Render(), nil
}

View File

@@ -0,0 +1,37 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestLink_MarshalYAML(t *testing.T) {
link := Link{
OperationRef: "somewhere",
OperationId: "somewhereOutThere",
Parameters: map[string]string{
"over": "theRainbow",
},
RequestBody: "hello?",
Description: "are you there?",
Server: &Server{
URL: "https://pb33f.io",
},
}
dat, _ := link.Render()
desired := `operationRef: somewhere
operationId: somewhereOutThere
parameters:
over: theRainbow
requestBody: hello?
description: are you there?
server:
url: https://pb33f.io`
assert.Equal(t, desired, strings.TrimSpace(string(dat)))
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
"sync" "sync"
) )
@@ -16,11 +17,11 @@ import (
// Each Media Type Object provides schema and examples for the media type identified by its key. // Each Media Type Object provides schema and examples for the media type identified by its key.
// - https://spec.openapis.org/oas/v3.1.0#media-type-object // - https://spec.openapis.org/oas/v3.1.0#media-type-object
type MediaType struct { type MediaType struct {
Schema *base.SchemaProxy Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*base.Example Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding map[string]*Encoding Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
low *low.MediaType low *low.MediaType
} }
@@ -43,6 +44,20 @@ func (m *MediaType) GoLow() *low.MediaType {
return m.low return m.low
} }
// Render will return a YAML representation of the MediaType object as a byte slice.
func (m *MediaType) Render() ([]byte, error) {
return yaml.Marshal(m)
}
// MarshalYAML will create a ready to render YAML representation of the MediaType object.
func (m *MediaType) MarshalYAML() (interface{}, error) {
if m == nil {
return nil, nil
}
nb := high.NewNodeBuilder(m, m.low)
return nb.Render(), nil
}
// ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler // ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler
// and easier to navigate high-level one. // and easier to navigate high-level one.
func ExtractContent(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.MediaType]) map[string]*MediaType { func ExtractContent(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.MediaType]) map[string]*MediaType {

View File

@@ -0,0 +1,49 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/stretchr/testify/assert"
"io/ioutil"
"strings"
"testing"
)
func TestMediaType_MarshalYAML(t *testing.T) {
// load the petstore spec
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil {
panic("broken something")
}
// create a new document and extract a media type object from it.
d := NewDocument(lowDoc)
mt := d.Paths.PathItems["/pet"].Put.RequestBody.Content["application/json"]
// render out the media type
yml, _ := mt.Render()
// the rendered output should be a ref to the media type.
op := `schema:
$ref: '#/components/schemas/Pet'`
assert.Equal(t, op, strings.TrimSpace(string(yml)))
// modify the media type to have an example
mt.Example = "testing a nice mutation"
op = `example: testing a nice mutation
schema:
$ref: '#/components/schemas/Pet'`
yml, _ = mt.Render()
assert.Equal(t, op, strings.TrimSpace(string(yml)))
}

View File

@@ -6,16 +6,17 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
) )
// OAuthFlow represents a high-level OpenAPI 3+ OAuthFlow object that is backed by a low-level one. // OAuthFlow represents a high-level OpenAPI 3+ OAuthFlow object that is backed by a low-level one.
// - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object // - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object
type OAuthFlow struct { type OAuthFlow struct {
AuthorizationUrl string AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenUrl string TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshUrl string RefreshUrl string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes map[string]string Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
low *low.OAuthFlow low *low.OAuthFlow
} }
@@ -39,3 +40,17 @@ func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow {
func (o *OAuthFlow) GoLow() *low.OAuthFlow { func (o *OAuthFlow) GoLow() *low.OAuthFlow {
return o.low return o.low
} }
// Render will return a YAML representation of the OAuthFlow object as a byte slice.
func (o *OAuthFlow) Render() ([]byte, error) {
return yaml.Marshal(o)
}
// MarshalYAML will create a ready to render YAML representation of the OAuthFlow object.
func (o *OAuthFlow) MarshalYAML() (interface{}, error) {
if o == nil {
return nil, nil
}
nb := high.NewNodeBuilder(o, o.low)
return nb.Render(), nil
}

View File

@@ -0,0 +1,44 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestOAuthFlow_MarshalYAML(t *testing.T) {
oflow := &OAuthFlow{
AuthorizationUrl: "https://pb33f.io",
TokenUrl: "https://pb33f.io/token",
RefreshUrl: "https://pb33f.io/refresh",
Scopes: map[string]string{"chicken": "nuggets", "beefy": "soup"},
}
rend, _ := oflow.Render()
desired := `authorizationUrl: https://pb33f.io
tokenUrl: https://pb33f.io/token
refreshUrl: https://pb33f.io/refresh
scopes:
chicken: nuggets
beefy: soup`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
// mutate
oflow.Scopes = nil
oflow.Extensions = map[string]interface{}{"x-burgers": "why not?"}
desired = `authorizationUrl: https://pb33f.io
tokenUrl: https://pb33f.io/token
refreshUrl: https://pb33f.io/refresh
x-burgers: why not?`
rend, _ = oflow.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}

View File

@@ -6,15 +6,16 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
) )
// Server represents a high-level OpenAPI 3+ Server object, that is backed by a low level one. // Server represents a high-level OpenAPI 3+ Server object, that is backed by a low level one.
// - https://spec.openapis.org/oas/v3.1.0#server-object // - https://spec.openapis.org/oas/v3.1.0#server-object
type Server struct { type Server struct {
URL string URL string `json:"url,omitempty" yaml:"url,omitempty"`
Description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables map[string]*ServerVariable Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
low *low.Server low *low.Server
} }
@@ -37,3 +38,17 @@ func NewServer(server *low.Server) *Server {
func (s *Server) GoLow() *low.Server { func (s *Server) GoLow() *low.Server {
return s.low return s.low
} }
// Render will return a YAML representation of the Server object as a byte slice.
func (s *Server) Render() ([]byte, error) {
return yaml.Marshal(s)
}
// MarshalYAML will create a ready to render YAML representation of the Server object.
func (s *Server) MarshalYAML() (interface{}, error) {
if s == nil {
return nil, nil
}
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
}

View File

@@ -0,0 +1,43 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestServer_MarshalYAML(t *testing.T) {
server := &Server{
URL: "https://pb33f.io",
Description: "the b33f",
}
desired := `url: https://pb33f.io
description: the b33f`
rend, _ := server.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
// mutate
server.Variables = map[string]*ServerVariable{
"rainbow": {
Enum: []string{"one", "two", "three"},
},
}
desired = `url: https://pb33f.io
description: the b33f
variables:
rainbow:
enum:
- one
- two
- three`
rend, _ = server.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}

View File

@@ -3,16 +3,20 @@
package v3 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"
)
// ServerVariable represents a high-level OpenAPI 3+ ServerVariable object, that is backed by a low-level one. // ServerVariable represents a high-level OpenAPI 3+ ServerVariable object, that is backed by a low-level one.
// //
// ServerVariable is an object representing a Server Variable for server URL template substitution. // ServerVariable is an object representing a Server Variable for server URL template substitution.
// - https://spec.openapis.org/oas/v3.1.0#server-variable-object // - https://spec.openapis.org/oas/v3.1.0#server-variable-object
type ServerVariable struct { type ServerVariable struct {
Enum []string Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
low *low.ServerVariable low *low.ServerVariable
} }
@@ -32,7 +36,21 @@ func NewServerVariable(variable *low.ServerVariable) *ServerVariable {
return v return v
} }
// GoLow returns the low-level ServerVariable used to to create the high\-level one. // GoLow returns the low-level ServerVariable used to create the high\-level one.
func (s *ServerVariable) GoLow() *low.ServerVariable { func (s *ServerVariable) GoLow() *low.ServerVariable {
return s.low return s.low
} }
// Render will return a YAML representation of the ServerVariable object as a byte slice.
func (s *ServerVariable) Render() ([]byte, error) {
return yaml.Marshal(s)
}
// MarshalYAML will create a ready to render YAML representation of the ServerVariable object.
func (s *ServerVariable) MarshalYAML() (interface{}, error) {
if s == nil {
return nil, nil
}
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
}

View File

@@ -0,0 +1,43 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestServerVariable_MarshalYAML(t *testing.T) {
svar := &ServerVariable{
Enum: []string{"one", "two", "three"},
Description: "money day",
}
desired := `enum:
- one
- two
- three
description: money day`
svarRend, _ := svar.Render()
assert.Equal(t, desired, strings.TrimSpace(string(svarRend)))
// mutate
svar.Default = "is moments away"
desired = `enum:
- one
- two
- three
default: is moments away
description: money day`
svarRend, _ = svar.Render()
assert.Equal(t, desired, strings.TrimSpace(string(svarRend)))
}

View File

@@ -424,8 +424,6 @@ func (index *SpecIndex) checkPolymorphicNode(name string) (bool, string) {
return false, "" return false, ""
} }
// GetPathCount will return the number of paths found in the spec // GetPathCount will return the number of paths found in the spec
func (index *SpecIndex) GetPathCount() int { func (index *SpecIndex) GetPathCount() int {
if index.root == nil { if index.root == nil {