mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 04:20:11 +00:00
(feat): Unpack extensions into complex function types #8
The more in-depth we use extensions, the more likely is is that we need custom extensions. New `UnpackExtensions` function in the high package makes this easy. Low level models have been updated to support feature fully and docs added in README and examples as well. Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
391
README.md
391
README.md
@@ -5,8 +5,27 @@
|
||||

|
||||
[](https://goreportcard.com/report/github.com/pb33f/libopenapi)
|
||||
[](https://codecov.io/gh/pb33f/libopenapi)
|
||||
[](https://pkg.go.dev/github.com/pb33f/libopenapi)
|
||||
|
||||
libopenapi has full support for Swagger (OpenAPI 2), OpenAPI 3, and OpenAPI 3.1.
|
||||
libopenapi has full support for Swagger (OpenAPI 2), OpenAPI 3, and OpenAPI 3.1. It can handle the largest and most
|
||||
complex specifications you can think of.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Installing libopenapi](#installing)
|
||||
- [Load an OpenAPI 3.1 or 3.0 specification into a model](#load-an-openapi-spec-into-a-model)
|
||||
- [Load a Swagger Spec into a model](#load-a-swagger-spec-into-a-model)
|
||||
- [Discover what changed](#discover-what-changed)
|
||||
- [Loading complex types using extensions](#loading-extensions-using-complex-types)
|
||||
- [Creating an index of an OpenAPI specification](#creating-an-index-of-an-openapi-specification)
|
||||
- [Resolving an OpenAPI specification](#resolving-an-openapi-specification)
|
||||
- [Checking for circular errors without resolving](#checking-for-circular-errors-without-resolving)
|
||||
- [Extracting circular refs and resolving errors when building a document](#extracting-circular-refs-and-resolving-errors-when-building-a-document)
|
||||
- [Mutating the model](#mutating-the-model)
|
||||
|
||||
> **Read the docs at [https://pkg.go.dev/github.com/pb33f/libopenapi](https://pkg.go.dev/github.com/pb33f/libopenapi)**
|
||||
>
|
||||
---
|
||||
|
||||
## Introduction - Why?
|
||||
|
||||
@@ -77,7 +96,7 @@ Grab the latest release of **libopenapi**
|
||||
go get github.com/pb33f/libopenapi
|
||||
```
|
||||
|
||||
### Load an OpenAPI 3+ spec into a model
|
||||
## Load an OpenAPI spec into a model
|
||||
|
||||
```go
|
||||
// import the library
|
||||
@@ -124,7 +143,7 @@ There are 13 paths and 8 schemas in the document
|
||||
```
|
||||
|
||||
|
||||
### Load a Swagger (OpenAPI 2) spec into a model
|
||||
## Load a Swagger spec into a model
|
||||
```go
|
||||
// import the library
|
||||
import "github.com/pb33f/libopenapi"
|
||||
@@ -135,7 +154,7 @@ func readSpec() {
|
||||
petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json")
|
||||
|
||||
// create a new document from specification bytes
|
||||
document, err := libeopnapi.NewDocument(petstore)
|
||||
document, err := libopenapi.NewDocument(petstore)
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
@@ -205,7 +224,7 @@ fmt.Printf("value is %s, the value is on line %d, " +
|
||||
```
|
||||
---
|
||||
|
||||
## What changed?
|
||||
## Discover what changed
|
||||
|
||||
libopenapi comes with a complete **diff engine**
|
||||
|
||||
@@ -260,97 +279,143 @@ Every change can be explored and navigated just like you would use the high or l
|
||||
|
||||
---
|
||||
|
||||
## But wait, there's more - Mutating the model
|
||||
## Loading extensions using complex types
|
||||
|
||||
Having a read-only model is great, but what about when we want to modify the model and mutate values, or even add new
|
||||
content to the model? What if we also want to save that output as an updated specification - but we don't want to
|
||||
jumble up the original ordering of the source.
|
||||
If you're using extensions with complex types (rather that just simple strings and primitives), then there is a good
|
||||
chance you're going to want some simple way to marshal extensions into those structs.
|
||||
|
||||
### marshaling and unmarshalling to and from structs into JSON/YAML is not ideal.
|
||||
Since version v0.4.0 There is a new method to make this simple available in the `high` package within the `datamodel`
|
||||
package.
|
||||
|
||||
When we straight up use `json.Marshal` or `yaml.Marshal` to send structs to be rendered into the desired format, there
|
||||
is no guarantee as to the order in which each component will be rendered. This works great if...
|
||||
|
||||
- We don't care about the spec being randomly ordered.
|
||||
- We don't care about code-reviews.
|
||||
- We don't actually care about this very much.
|
||||
|
||||
### But if we do care...
|
||||
|
||||
Then libopenpi provides a way to mutate the model, that keeps the original [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node)
|
||||
tree in-tact. It allows us to make changes to values in place, and serialize back to JSON or YAML without any changes to
|
||||
other content order.
|
||||
Here is an example of complex types being extracted easily from OpenAPI extensions.
|
||||
|
||||
```go
|
||||
// create very small, and useless spec that does nothing useful, except showcase this feature.
|
||||
spec := `
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: This is a title
|
||||
contact:
|
||||
name: Some Person
|
||||
email: some@emailaddress.com
|
||||
license:
|
||||
url: http://some-place-on-the-internet.com/license
|
||||
`
|
||||
// create a new document from specification bytes
|
||||
document, err := libopenapi.NewDocument([]byte(spec))
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
import (
|
||||
"github.com/pb33f/libopenapi"
|
||||
high "github.com/pb33f/libopenapi/datamodel/high"
|
||||
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
||||
)
|
||||
|
||||
// define an example struct representing a cake
|
||||
type cake struct {
|
||||
Candles int
|
||||
Frosting string
|
||||
}
|
||||
|
||||
// define a struct that holds a map of cake pointers.
|
||||
type cakes struct {
|
||||
Description string
|
||||
Cakes map[string]*cake
|
||||
}
|
||||
|
||||
// define a struct representing a burger
|
||||
type burger struct {
|
||||
Sauce string
|
||||
Patty string
|
||||
}
|
||||
|
||||
// define a struct that holds a map of cake pointers
|
||||
type burgers struct {
|
||||
Description string
|
||||
Burgers map[string]*burger
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// create a specification with a schema and parameter that use complex
|
||||
// custom cakes and burgers extensions.
|
||||
spec := `openapi: "3.1"
|
||||
components:
|
||||
schemas:
|
||||
SchemaOne:
|
||||
description: "Some schema with custom complex extensions"
|
||||
x-custom-cakes:
|
||||
description: some cakes
|
||||
cakes:
|
||||
someCake:
|
||||
candles: 10
|
||||
frosting: blue
|
||||
anotherCake:
|
||||
candles: 1
|
||||
frosting: green
|
||||
parameters:
|
||||
ParameterOne:
|
||||
description: "Some parameter also using complex extensions"
|
||||
x-custom-burgers:
|
||||
description: some burgers
|
||||
burgers:
|
||||
someBurger:
|
||||
sauce: ketchup
|
||||
patty: meat
|
||||
anotherBurger:
|
||||
sauce: mayo
|
||||
patty: lamb`
|
||||
|
||||
// create a new document from specification bytes
|
||||
doc, err := libopenapi.NewDocument([]byte(spec))
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
|
||||
// because we know this is a v3 spec, we can build a ready to go model from it.
|
||||
v3Model, errors := document.BuildV3Model()
|
||||
|
||||
// if anything went wrong when building the v3 model, a slice of errors will be returned
|
||||
if len(errors) > 0 {
|
||||
for i := range errors {
|
||||
fmt.Printf("error: %e\n", errors[i])
|
||||
}
|
||||
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
|
||||
|
||||
// build a v3 model.
|
||||
docModel, errs := doc.BuildV3Model()
|
||||
|
||||
// if anything went wrong building, indexing and resolving the model, an error is thrown
|
||||
if errs != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
|
||||
// get a reference to SchemaOne and ParameterOne
|
||||
schemaOne := docModel.Model.Components.Schemas["SchemaOne"].Schema()
|
||||
parameterOne := docModel.Model.Components.Parameters["ParameterOne"]
|
||||
|
||||
// unpack schemaOne extensions into complex `cakes` type
|
||||
schemaOneExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *lowbase.Schema](schemaOne)
|
||||
if schemaUnpackErrors != nil {
|
||||
panic(fmt.Sprintf("cannot unpack schema extensions: %e", err))
|
||||
}
|
||||
|
||||
// unpack parameterOne into complex `burgers` type
|
||||
parameterOneExtensions, paramUnpackErrors := high.UnpackExtensions[burgers, *lowv3.Parameter](parameterOne)
|
||||
if paramUnpackErrors != nil {
|
||||
panic(fmt.Sprintf("cannot unpack parameter extensions: %e", err))
|
||||
}
|
||||
|
||||
// extract extension by name for schemaOne
|
||||
customCakes := schemaOneExtensions["x-custom-cakes"]
|
||||
|
||||
// extract extension by name for schemaOne
|
||||
customBurgers := parameterOneExtensions["x-custom-burgers"]
|
||||
|
||||
// print out schemaOne complex extension details.
|
||||
fmt.Printf("schemaOne 'x-custom-cakes' (%s) has %d cakes, 'someCake' has %d candles and %s frosting\n",
|
||||
customCakes.Description,
|
||||
len(customCakes.Cakes),
|
||||
customCakes.Cakes["someCake"].Candles,
|
||||
customCakes.Cakes["someCake"].Frosting,
|
||||
)
|
||||
|
||||
// print out parameterOne complex extension details.
|
||||
fmt.Printf("parameterOne 'x-custom-burgers' (%s) has %d burgers, 'anotherBurger' has %s sauce and a %s patty\n",
|
||||
customBurgers.Description,
|
||||
len(customBurgers.Burgers),
|
||||
customBurgers.Burgers["anotherBurger"].Sauce,
|
||||
customBurgers.Burgers["anotherBurger"].Patty,
|
||||
)
|
||||
}
|
||||
|
||||
// mutate the title, to do this we currently need to drop down to the low-level API.
|
||||
v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec")
|
||||
|
||||
// mutate the email address in the contact object.
|
||||
v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io")
|
||||
|
||||
// mutate the name in the contact object.
|
||||
v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo")
|
||||
|
||||
// mutate the URL for the license object.
|
||||
v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license")
|
||||
|
||||
// serialize the document back into the original YAML or JSON
|
||||
mutatedSpec, serialError := document.Serialize()
|
||||
|
||||
// if something went wrong serializing
|
||||
if serialError != nil {
|
||||
panic(fmt.Sprintf("cannot serialize document: %e", serialError))
|
||||
}
|
||||
|
||||
// print our modified spec!
|
||||
fmt.Println(string(mutatedSpec))
|
||||
```
|
||||
|
||||
Which will output:
|
||||
|
||||
```yaml
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: A new title for a useless spec
|
||||
contact:
|
||||
name: Buckaroo
|
||||
email: buckaroo@pb33f.io
|
||||
license:
|
||||
url: https://pb33f.io/license
|
||||
This will output:
|
||||
|
||||
```text
|
||||
schemaOne 'x-custom-cakes' (some cakes) has 2 cakes, 'someCake' has 10 candles and blue frosting
|
||||
parameterOne 'x-custom-burgers' (some burgers) has 2 burgers, 'anotherBurger' has mayo sauce and a lamb patty
|
||||
```
|
||||
> It's worth noting that the original line numbers and column numbers **won't be respected** when calling `Serialize()`,
|
||||
> A new `Document` needs to be created from that raw YAML to continue processing after serialization.
|
||||
---
|
||||
|
||||
## Creating an index of an OpenAPI Specification
|
||||
|
||||
@@ -518,8 +583,168 @@ fmt.Printf("There are %d circular reference errors, " +
|
||||
len(resolver.GetNonPolymorphicCircularErrors()))
|
||||
```
|
||||
|
||||
### Extracting circular refs and resolving errors when building a document
|
||||
|
||||
To avoid having to create an index and a resolver each time you want to both create
|
||||
a document and resolve it / check for errors, don't worry, circular references are checked
|
||||
automatically and are available in the returned `[]errors` which building a document.
|
||||
|
||||
The errors returned by the slice are pointers to `*resolver.ResolvingError` which contains
|
||||
rich details about the issue, where it was found and the journey it took to get there.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```go
|
||||
// create a specification with an obvious and deliberate circular reference
|
||||
spec := `
|
||||
openapi: "3.1"
|
||||
components:
|
||||
schemas:
|
||||
One:
|
||||
description: "test one"
|
||||
properties:
|
||||
things:
|
||||
"$ref": "#/components/schemas/Two"
|
||||
Two:
|
||||
description: "test two"
|
||||
properties:
|
||||
testThing:
|
||||
"$ref": "#/components/schemas/One"
|
||||
`
|
||||
// create a new document from specification bytes
|
||||
doc, err := NewDocument([]byte(spec))
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
_, errs := doc.BuildV3Model()
|
||||
|
||||
// extract resolving error
|
||||
resolvingError := errs[0]
|
||||
|
||||
// resolving error is a pointer to *resolver.ResolvingError
|
||||
// which provides access to rich details about the error.
|
||||
circularReference := resolvingError.(*resolver.ResolvingError).CircularReference
|
||||
|
||||
// capture the journey with all details
|
||||
var buf strings.Builder
|
||||
for n := range circularReference.Journey {
|
||||
|
||||
// add the full definition name to the journey.
|
||||
buf.WriteString(circularReference.Journey[n].Definition)
|
||||
if n < len(circularReference.Journey)-1 {
|
||||
buf.WriteString(" -> ")
|
||||
}
|
||||
}
|
||||
|
||||
// print out the journey and the loop point.
|
||||
fmt.Printf("Journey: %s\n", buf.String())
|
||||
fmt.Printf("Loop Point: %s", circularReference.LoopPoint.Definition)
|
||||
```
|
||||
|
||||
Will output:
|
||||
|
||||
```text
|
||||
Journey: #/components/schemas/Two -> #/components/schemas/One -> #/components/schemas/Two
|
||||
Loop Point: #/components/schemas/Two
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Mutating the model
|
||||
|
||||
Having a read-only model is great, but what about when we want to modify the model and mutate values, or even add new
|
||||
content to the model? What if we also want to save that output as an updated specification - but we don't want to
|
||||
jumble up the original ordering of the source.
|
||||
|
||||
### marshaling and unmarshalling to and from structs into JSON/YAML is not ideal.
|
||||
|
||||
When we straight up use `json.Marshal` or `yaml.Marshal` to send structs to be rendered into the desired format, there
|
||||
is no guarantee as to the order in which each component will be rendered. This works great if...
|
||||
|
||||
- We don't care about the spec being randomly ordered.
|
||||
- We don't care about code-reviews.
|
||||
- We don't actually care about this very much.
|
||||
|
||||
### But if we do care...
|
||||
|
||||
Then libopenpi provides a way to mutate the model, that keeps the original [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node)
|
||||
tree in-tact. It allows us to make changes to values in place, and serialize back to JSON or YAML without any changes to
|
||||
other content order.
|
||||
|
||||
```go
|
||||
// create very small, and useless spec that does nothing useful, except showcase this feature.
|
||||
spec := `
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: This is a title
|
||||
contact:
|
||||
name: Some Person
|
||||
email: some@emailaddress.com
|
||||
license:
|
||||
url: http://some-place-on-the-internet.com/license
|
||||
`
|
||||
// create a new document from specification bytes
|
||||
document, err := libopenapi.NewDocument([]byte(spec))
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
|
||||
// because we know this is a v3 spec, we can build a ready to go model from it.
|
||||
v3Model, errors := document.BuildV3Model()
|
||||
|
||||
// if anything went wrong when building the v3 model, a slice of errors will be returned
|
||||
if len(errors) > 0 {
|
||||
for i := range errors {
|
||||
fmt.Printf("error: %e\n", errors[i])
|
||||
}
|
||||
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
|
||||
}
|
||||
|
||||
// mutate the title, to do this we currently need to drop down to the low-level API.
|
||||
v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec")
|
||||
|
||||
// mutate the email address in the contact object.
|
||||
v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io")
|
||||
|
||||
// mutate the name in the contact object.
|
||||
v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo")
|
||||
|
||||
// mutate the URL for the license object.
|
||||
v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license")
|
||||
|
||||
// serialize the document back into the original YAML or JSON
|
||||
mutatedSpec, serialError := document.Serialize()
|
||||
|
||||
// if something went wrong serializing
|
||||
if serialError != nil {
|
||||
panic(fmt.Sprintf("cannot serialize document: %e", serialError))
|
||||
}
|
||||
|
||||
// print our modified spec!
|
||||
fmt.Println(string(mutatedSpec))
|
||||
```
|
||||
|
||||
Which will output:
|
||||
|
||||
```yaml
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: A new title for a useless spec
|
||||
contact:
|
||||
name: Buckaroo
|
||||
email: buckaroo@pb33f.io
|
||||
license:
|
||||
url: https://pb33f.io/license
|
||||
|
||||
```
|
||||
> It's worth noting that the original line numbers and column numbers **won't be respected** when calling `Serialize()`,
|
||||
> A new `Document` needs to be created from that raw YAML to continue processing after serialization.
|
||||
|
||||
> **Read the full docs at [https://pkg.go.dev](https://pkg.go.dev/github.com/pb33f/libopenapi)**
|
||||
|
||||
---
|
||||
|
||||
@@ -38,3 +38,7 @@ func NewExternalDoc(extDoc *low.ExternalDoc) *ExternalDoc {
|
||||
func (e *ExternalDoc) GoLow() *low.ExternalDoc {
|
||||
return e.low
|
||||
}
|
||||
|
||||
func (e *ExternalDoc) GetExtensions() map[string]any {
|
||||
return e.Extensions
|
||||
}
|
||||
@@ -35,6 +35,7 @@ x-hack: code`
|
||||
|
||||
wentLow := highExt.GoLow()
|
||||
assert.Equal(t, 2, wentLow.URL.ValueNode.Line)
|
||||
assert.Len(t, highExt.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func ExampleNewExternalDoc() {
|
||||
|
||||
@@ -33,3 +33,34 @@ func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReferenc
|
||||
}
|
||||
return extracted
|
||||
}
|
||||
|
||||
// UnpackExtensions is a convenience function that makes it easy and simple to unpack an objects extensions
|
||||
// into a complex type, provided as a generic. This function is for high-level models that implement `GoesLow()`
|
||||
// and for low-level models that support extensions via `HasExtensions`.
|
||||
//
|
||||
// This feature will be upgraded at some point to hold a registry of types and extension mappings to make this
|
||||
// functionality available a little more automatically.
|
||||
// You can read more about the discussion here: https://github.com/pb33f/libopenapi/issues/8
|
||||
//
|
||||
// `T` represents the Type you want to unpack into
|
||||
// `R` represents the LOW type of the object that contains the extensions (not the high)
|
||||
// `low` represents the HIGH type of the object that contains the extensions.
|
||||
//
|
||||
// to use:
|
||||
// schema := schemaProxy.Schema() // any high-level object that has extensions
|
||||
// extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema)
|
||||
func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) {
|
||||
m := make(map[string]*T)
|
||||
ext := low.GoLow().GetExtensions()
|
||||
for i := range ext {
|
||||
key := i.Value
|
||||
g := new(T)
|
||||
valueNode := ext[i].ValueNode
|
||||
err := valueNode.Decode(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[key] = g
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package high
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -19,3 +20,111 @@ func TestExtractExtensions(t *testing.T) {
|
||||
ext := ExtractExtensions(n)
|
||||
assert.Equal(t, "new cowboy in town", ext["pb33f"])
|
||||
}
|
||||
|
||||
type textExtension struct {
|
||||
Cowboy string
|
||||
Power int
|
||||
}
|
||||
|
||||
type parent struct {
|
||||
low *child
|
||||
}
|
||||
|
||||
func (p *parent) GoLow() *child {
|
||||
return p.low
|
||||
}
|
||||
|
||||
type child struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
func (c *child) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return c.Extensions
|
||||
}
|
||||
|
||||
func TestUnpackExtensions(t *testing.T) {
|
||||
|
||||
var resultA, resultB yaml.Node
|
||||
|
||||
ymlA := `
|
||||
cowboy: buckaroo
|
||||
power: 100`
|
||||
|
||||
ymlB := `
|
||||
cowboy: frogman
|
||||
power: 2`
|
||||
|
||||
err := yaml.Unmarshal([]byte(ymlA), &resultA)
|
||||
assert.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte(ymlB), &resultB)
|
||||
assert.NoError(t, err)
|
||||
|
||||
n := make(map[low.KeyReference[string]]low.ValueReference[any])
|
||||
n[low.KeyReference[string]{
|
||||
Value: "x-rancher-a",
|
||||
}] = low.ValueReference[any]{
|
||||
ValueNode: resultA.Content[0],
|
||||
}
|
||||
|
||||
n[low.KeyReference[string]{
|
||||
Value: "x-rancher-b",
|
||||
}] = low.ValueReference[any]{
|
||||
ValueNode: resultB.Content[0],
|
||||
}
|
||||
|
||||
c := new(child)
|
||||
c.Extensions = n
|
||||
|
||||
p := new(parent)
|
||||
p.low = c
|
||||
|
||||
res, err := UnpackExtensions[textExtension, *child](p)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, res)
|
||||
assert.Equal(t, "buckaroo", res["x-rancher-a"].Cowboy)
|
||||
assert.Equal(t, 100, res["x-rancher-a"].Power)
|
||||
assert.Equal(t, "frogman", res["x-rancher-b"].Cowboy)
|
||||
assert.Equal(t, 2, res["x-rancher-b"].Power)
|
||||
}
|
||||
|
||||
func TestUnpackExtensions_Fail(t *testing.T) {
|
||||
|
||||
var resultA, resultB yaml.Node
|
||||
|
||||
ymlA := `
|
||||
cowboy: buckaroo
|
||||
power: 100`
|
||||
|
||||
// this is incorrect types, unpacking will fail.
|
||||
ymlB := `
|
||||
cowboy: 0
|
||||
power: hello`
|
||||
|
||||
err := yaml.Unmarshal([]byte(ymlA), &resultA)
|
||||
assert.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte(ymlB), &resultB)
|
||||
assert.NoError(t, err)
|
||||
|
||||
n := make(map[low.KeyReference[string]]low.ValueReference[any])
|
||||
n[low.KeyReference[string]{
|
||||
Value: "x-rancher-a",
|
||||
}] = low.ValueReference[any]{
|
||||
ValueNode: resultA.Content[0],
|
||||
}
|
||||
|
||||
n[low.KeyReference[string]{
|
||||
Value: "x-rancher-b",
|
||||
}] = low.ValueReference[any]{
|
||||
ValueNode: resultB.Content[0],
|
||||
}
|
||||
|
||||
c := new(child)
|
||||
c.Extensions = n
|
||||
|
||||
p := new(parent)
|
||||
p.low = c
|
||||
|
||||
res, er := UnpackExtensions[textExtension, *child](p)
|
||||
assert.Error(t, er)
|
||||
assert.Empty(t, res)
|
||||
}
|
||||
@@ -35,6 +35,11 @@ func (i *Info) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap(ext, i.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all extensions for Info
|
||||
func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return i.Extensions
|
||||
}
|
||||
|
||||
// Build will extract out the Contact and Info objects from the supplied root node.
|
||||
func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
i.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -55,6 +55,7 @@ x-cli-name: pizza cli`
|
||||
cliName := n.FindExtension("x-cli-name")
|
||||
assert.NotNil(t, cliName)
|
||||
assert.Equal(t, "pizza cli", cliName.Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestContact_Build(t *testing.T) {
|
||||
|
||||
@@ -381,6 +381,11 @@ func (s *Schema) FindProperty(name string) *low.ValueReference[*SchemaProxy] {
|
||||
return low.FindItemInMap[*SchemaProxy](name, s.Properties.Value)
|
||||
}
|
||||
|
||||
// GetExtensions returns all extensions for Schema
|
||||
func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return s.Extensions
|
||||
}
|
||||
|
||||
// Build will perform a number of operations.
|
||||
// Extraction of the following happens in this method:
|
||||
// - Extensions
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
|
||||
func TestSchemaProxy_Build(t *testing.T) {
|
||||
|
||||
yml := `description: something`
|
||||
yml := `x-windows: washed
|
||||
description: something`
|
||||
|
||||
var sch SchemaProxy
|
||||
var idxNode yaml.Node
|
||||
@@ -21,7 +22,7 @@ func TestSchemaProxy_Build(t *testing.T) {
|
||||
err := sch.Build(idxNode.Content[0], nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb",
|
||||
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23",
|
||||
low.GenerateHashString(&sch))
|
||||
|
||||
assert.Equal(t, "something", sch.Schema().Description.Value)
|
||||
@@ -30,9 +31,11 @@ func TestSchemaProxy_Build(t *testing.T) {
|
||||
assert.False(t, sch.IsSchemaReference())
|
||||
|
||||
// already rendered, should spit out the same
|
||||
assert.Equal(t, "3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb",
|
||||
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23",
|
||||
low.GenerateHashString(&sch))
|
||||
|
||||
assert.Len(t, sch.Schema().GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
func TestSchemaProxy_Build_CheckRef(t *testing.T) {
|
||||
|
||||
@@ -33,6 +33,7 @@ func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface.
|
||||
func (x *XML) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return x.Extensions
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ type Hashable interface {
|
||||
|
||||
// HasExtensions is implemented by any object that exposes extensions
|
||||
type HasExtensions[T any] interface {
|
||||
|
||||
// GetExtensions returns generic low level extensions
|
||||
GetExtensions() map[KeyReference[string]]ValueReference[any]
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ func (h *Header) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, h.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Header extensions and satisfies the low.HasExtensions interface.
|
||||
func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return h.Extensions
|
||||
}
|
||||
|
||||
// Build will build out items, extensions and default value from the supplied node.
|
||||
func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
h.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -32,7 +32,8 @@ func TestHeader_Build(t *testing.T) {
|
||||
|
||||
func TestHeader_DefaultAsSlice(t *testing.T) {
|
||||
|
||||
yml := `default:
|
||||
yml := `x-ext: thing
|
||||
default:
|
||||
- why
|
||||
- so many
|
||||
- variations`
|
||||
@@ -47,7 +48,7 @@ func TestHeader_DefaultAsSlice(t *testing.T) {
|
||||
|
||||
assert.NotNil(t, n.Default.Value)
|
||||
assert.Len(t, n.Default.Value, 3)
|
||||
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestHeader_DefaultAsObject(t *testing.T) {
|
||||
|
||||
@@ -45,6 +45,11 @@ func (i *Items) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, i.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Items extensions and satisfies the low.HasExtensions interface.
|
||||
func (i *Items) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return i.Extensions
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the Items object
|
||||
func (i *Items) Hash() [32]byte {
|
||||
var f []string
|
||||
|
||||
@@ -31,7 +31,8 @@ func TestItems_Build(t *testing.T) {
|
||||
|
||||
func TestItems_DefaultAsSlice(t *testing.T) {
|
||||
|
||||
yml := `default:
|
||||
yml := `x-thing: thing
|
||||
default:
|
||||
- pizza
|
||||
- cake`
|
||||
|
||||
@@ -44,6 +45,7 @@ func TestItems_DefaultAsSlice(t *testing.T) {
|
||||
_ = n.Build(idxNode.Content[0], idx)
|
||||
|
||||
assert.Len(t, n.Default.Value, 2)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestItems_DefaultAsMap(t *testing.T) {
|
||||
|
||||
@@ -78,6 +78,11 @@ func (p *Parameter) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, p.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Parameter extensions and satisfies the low.HasExtensions interface.
|
||||
func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return p.Extensions
|
||||
}
|
||||
|
||||
// Build will extract out extensions, schema, items and default value
|
||||
func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
p.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -207,5 +207,6 @@ allowEmptyValue: true
|
||||
assert.Equal(t, "int", v.Value.A) // A is v2
|
||||
assert.True(t, n.GetRequired().Value)
|
||||
assert.True(t, n.GetAllowEmptyValue().Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ func (p *PathItem) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, p.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all PathItem extensions and satisfies the low.HasExtensions interface.
|
||||
func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return p.Extensions
|
||||
}
|
||||
|
||||
// Build will extract extensions, parameters and operations for all methods. Every method is handled
|
||||
// asynchronously, in order to keep things moving quickly for complex operations.
|
||||
func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
|
||||
@@ -107,5 +107,6 @@ parameters:
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ type Paths struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Paths extensions and satisfies the low.HasExtensions interface.
|
||||
func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return p.Extensions
|
||||
}
|
||||
|
||||
// FindPath attempts to locate a PathItem instance, given a path key.
|
||||
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
|
||||
for k, j := range p.PathItems {
|
||||
|
||||
@@ -96,5 +96,6 @@ x-milk: creamy`
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ func (r *Response) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, r.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Response extensions and satisfies the low.HasExtensions interface.
|
||||
func (r *Response) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return r.Extensions
|
||||
}
|
||||
|
||||
// FindHeader will attempt to locate a Header value, given a key
|
||||
func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] {
|
||||
return low.FindItemInMap[*Header](hType, r.Headers.Value)
|
||||
|
||||
@@ -110,4 +110,5 @@ headers:
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ type Responses struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Responses extensions and satisfies the low.HasExtensions interface.
|
||||
func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return r.Extensions
|
||||
}
|
||||
|
||||
// Build will extract default value and extensions from node.
|
||||
func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
r.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -114,5 +114,6 @@ x-tea: warm`
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ type Scopes struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Scopes extensions and satisfies the low.HasExtensions interface.
|
||||
func (s *Scopes) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return s.Extensions
|
||||
}
|
||||
|
||||
// FindScope will attempt to locate a scope string using a key.
|
||||
func (s *Scopes) FindScope(scope string) *low.ValueReference[string] {
|
||||
return low.FindItemInMap[string](scope, s.Values)
|
||||
|
||||
@@ -39,5 +39,6 @@ burgers: chips`
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ type SecurityScheme struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all SecurityScheme extensions and satisfies the low.HasExtensions interface.
|
||||
func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return ss.Extensions
|
||||
}
|
||||
|
||||
// Build will extract extensions and scopes from the node.
|
||||
func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
ss.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -94,5 +94,6 @@ authorizationUrl: https://pb33f.io
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
@@ -116,6 +116,11 @@ func (s *Swagger) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, s.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Swagger/Top level extensions and satisfies the low.HasExtensions interface.
|
||||
func (s *Swagger) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return s.Extensions
|
||||
}
|
||||
|
||||
func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
|
||||
|
||||
doc := Swagger{Swagger: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}}
|
||||
|
||||
@@ -60,6 +60,7 @@ func TestCreateDocument(t *testing.T) {
|
||||
assert.Equal(t, true, doc.FindExtension("x-pet").Value)
|
||||
assert.Equal(t, true, doc.FindExtension("X-Pet").Value)
|
||||
assert.NotNil(t, doc.GetExternalDocs())
|
||||
assert.Len(t, doc.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestCreateDocument_Info(t *testing.T) {
|
||||
|
||||
@@ -25,6 +25,11 @@ type Callback struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Callback extensions and satisfies the low.HasExtensions interface.
|
||||
func (cb *Callback) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return cb.Extensions
|
||||
}
|
||||
|
||||
// FindExpression will locate a string expression and return a ValueReference containing the located PathItem
|
||||
func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] {
|
||||
return low.FindItemInMap[*PathItem](exp, cb.Expression.Value)
|
||||
|
||||
@@ -150,5 +150,6 @@ beer:
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 2)
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ type Components struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Components extensions and satisfies the low.HasExtensions interface.
|
||||
func (co *Components) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return co.Extensions
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the Encoding object
|
||||
func (co *Components) Hash() [32]byte {
|
||||
var f []string
|
||||
|
||||
@@ -221,7 +221,7 @@ func TestComponents_Build_HashEmpty(t *testing.T) {
|
||||
err = n.Build(idxNode.Content[0], idx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "seagull", n.FindExtension("x-curry").Value)
|
||||
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
assert.Equal(t, "9cf2c6ab3f9ff7e5231fcb391c8af5c47406711d2ca366533f21a8bb2f67edfe",
|
||||
low.GenerateHashString(&n))
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ func TestCreateDocument(t *testing.T) {
|
||||
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
|
||||
assert.NotEmpty(t, doc.Info.Value.Title.Value)
|
||||
assert.Equal(t, "https://pb33f.io/schema", doc.JsonSchemaDialect.Value)
|
||||
assert.Len(t, doc.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestCreateDocument_Info(t *testing.T) {
|
||||
|
||||
@@ -96,6 +96,11 @@ func (d *Document) FindSecurityRequirement(name string) []low.ValueReference[str
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExtensions returns all Document extensions and satisfies the low.HasExtensions interface.
|
||||
func (d *Document) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return d.Extensions
|
||||
}
|
||||
|
||||
func (d *Document) GetExternalDocs() *low.NodeReference[any] {
|
||||
return &low.NodeReference[any]{
|
||||
KeyNode: d.ExternalDocs.KeyNode,
|
||||
|
||||
@@ -47,6 +47,11 @@ func (h *Header) FindContent(ext string) *low.ValueReference[*MediaType] {
|
||||
return low.FindItemInMap[*MediaType](ext, h.Content.Value)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Header extensions and satisfies the low.HasExtensions interface.
|
||||
func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return h.Extensions
|
||||
}
|
||||
|
||||
// Hash will return a consistent SHA256 Hash of the Header object
|
||||
func (h *Header) Hash() [32]byte {
|
||||
var f []string
|
||||
|
||||
@@ -82,6 +82,7 @@ content:
|
||||
|
||||
ext := n.FindExtension("x-family-love").Value
|
||||
assert.Equal(t, "strong", ext)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestHeader_Build_Success_Examples(t *testing.T) {
|
||||
|
||||
@@ -35,6 +35,11 @@ type Link struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Link extensions and satisfies the low.HasExtensions interface.
|
||||
func (l *Link) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return l.Extensions
|
||||
}
|
||||
|
||||
// FindParameter will attempt to locate a parameter string value, using a parameter name input.
|
||||
func (l *Link) FindParameter(pName string) *low.ValueReference[string] {
|
||||
return low.FindItemInMap[string](pName, l.Parameters)
|
||||
|
||||
@@ -51,6 +51,7 @@ x-linky: slinky
|
||||
|
||||
assert.NotNil(t, n.Server.Value)
|
||||
assert.Equal(t, "https://pb33f.io", n.Server.Value.URL.Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ type MediaType struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all MediaType extensions and satisfies the low.HasExtensions interface.
|
||||
func (mt *MediaType) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return mt.Extensions
|
||||
}
|
||||
|
||||
// FindExtension will attempt to locate an extension with the supplied name.
|
||||
func (mt *MediaType) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, mt.Extensions)
|
||||
|
||||
@@ -141,4 +141,5 @@ example: a thing`
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ type OAuthFlows struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all OAuthFlows extensions and satisfies the low.HasExtensions interface.
|
||||
func (o *OAuthFlows) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return o.Extensions
|
||||
}
|
||||
|
||||
// FindExtension will attempt to locate an extension with the supplied name.
|
||||
func (o *OAuthFlows) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, o.Extensions)
|
||||
@@ -89,6 +94,11 @@ type OAuthFlow struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all OAuthFlow extensions and satisfies the low.HasExtensions interface.
|
||||
func (o *OAuthFlow) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return o.Extensions
|
||||
}
|
||||
|
||||
// FindScope attempts to locate a scope using a specified name.
|
||||
func (o *OAuthFlow) FindScope(scope string) *low.ValueReference[string] {
|
||||
return low.FindItemInMap[string](scope, o.Scopes.Value)
|
||||
|
||||
@@ -37,6 +37,7 @@ x-tasty: herbs
|
||||
assert.Equal(t, "https://pb33f.io/token", n.TokenUrl.Value)
|
||||
assert.Equal(t, "https://pb33f.io/refresh", n.RefreshUrl.Value)
|
||||
assert.Equal(t, "vanilla", n.FindScope("fresh:cake").Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestOAuthFlow_Build_Implicit(t *testing.T) {
|
||||
@@ -57,6 +58,7 @@ x-tasty: herbs`
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "herbs", n.FindExtension("x-tasty").Value)
|
||||
assert.Equal(t, "https://pb33f.io/auth", n.Implicit.Value.AuthorizationUrl.Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestOAuthFlow_Build_Implicit_Fail(t *testing.T) {
|
||||
|
||||
@@ -51,6 +51,11 @@ func (p *Parameter) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, p.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all extensions for Parameter.
|
||||
func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return p.Extensions
|
||||
}
|
||||
|
||||
// Build will extract examples, extensions and content/media types.
|
||||
func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
p.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -86,6 +86,7 @@ content:
|
||||
|
||||
ext := n.FindExtension("x-family-love").Value
|
||||
assert.Equal(t, "strong", ext)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestParameter_Build_Success_Examples(t *testing.T) {
|
||||
|
||||
@@ -99,6 +99,11 @@ func (p *PathItem) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, p.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all PathItem extensions and satisfies the low.HasExtensions interface.
|
||||
func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return p.Extensions
|
||||
}
|
||||
|
||||
// Build extracts extensions, parameters, servers and each http method defined.
|
||||
// everything is extracted asynchronously for speed.
|
||||
func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
|
||||
@@ -79,4 +79,5 @@ summary: it's another path item`
|
||||
|
||||
// hash
|
||||
assert.Equal(t, n.Hash(), n2.Hash())
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, p.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all Paths extensions and satisfies the low.HasExtensions interface.
|
||||
func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return p.Extensions
|
||||
}
|
||||
|
||||
// Build will extract extensions and all PathItems. This happens asynchronously for speed.
|
||||
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
p.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -62,6 +62,7 @@ x-milk: cold`
|
||||
assert.Len(t, path.Parameters.Value, 1)
|
||||
assert.Equal(t, "cold", n.FindExtension("x-milk").Value)
|
||||
assert.Equal(t, "hello", path.Parameters.Value[0].Value.Name.Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
}
|
||||
|
||||
func TestPaths_Build_Fail(t *testing.T) {
|
||||
|
||||
@@ -27,6 +27,11 @@ func (rb *RequestBody) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, rb.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all RequestBody extensions and satisfies the low.HasExtensions interface.
|
||||
func (rb *RequestBody) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return rb.Extensions
|
||||
}
|
||||
|
||||
// FindContent attempts to find content/MediaType defined using a specified name.
|
||||
func (rb *RequestBody) FindContent(cType string) *low.ValueReference[*MediaType] {
|
||||
return low.FindItemInMap[*MediaType](cType, rb.Content.Value)
|
||||
|
||||
@@ -34,6 +34,7 @@ x-requesto: presto`
|
||||
assert.True(t, n.Required.Value)
|
||||
assert.Equal(t, "nice.", n.FindContent("fresh/fish").Value.Example.Value)
|
||||
assert.Equal(t, "presto", n.FindExtension("x-requesto").Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ func (r *Response) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, r.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all OAuthFlow extensions and satisfies the low.HasExtensions interface.
|
||||
func (r *Response) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return r.Extensions
|
||||
}
|
||||
|
||||
// FindContent will attempt to locate a MediaType instance using the supplied key.
|
||||
func (r *Response) FindContent(cType string) *low.ValueReference[*MediaType] {
|
||||
return low.FindItemInMap[*MediaType](cType, r.Content.Value)
|
||||
|
||||
@@ -64,6 +64,7 @@ default:
|
||||
assert.Equal(t, "c009b2046101bc03df802b4cf23f78176931137e6115bf7b445ca46856c06b51",
|
||||
low.GenerateHashString(&n))
|
||||
|
||||
|
||||
}
|
||||
|
||||
func TestResponses_NoDefault(t *testing.T) {
|
||||
@@ -98,6 +99,9 @@ x-shoes: old`
|
||||
assert.Equal(t, "54ab66e6cb8bd226940f421c2387e45215b84c946182435dfe2a3036043fa07c",
|
||||
low.GenerateHashString(&n))
|
||||
|
||||
assert.Len(t, n.FindResponseByCode("200").Value.GetExtensions(), 1)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
func TestResponses_Build_FailCodes_WrongType(t *testing.T) {
|
||||
|
||||
@@ -38,6 +38,11 @@ type Responses struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Responses extensions and satisfies the low.HasExtensions interface.
|
||||
func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return r.Extensions
|
||||
}
|
||||
|
||||
// Build will extract default response and all Response objects for each code
|
||||
func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
r.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -58,6 +58,11 @@ func (ss *SecurityScheme) FindExtension(ext string) *low.ValueReference[any] {
|
||||
return low.FindItemInMap[any](ext, ss.Extensions)
|
||||
}
|
||||
|
||||
// GetExtensions returns all SecurityScheme extensions and satisfies the low.HasExtensions interface.
|
||||
func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return ss.Extensions
|
||||
}
|
||||
|
||||
// Build will extract OAuthFlows and extensions from the node.
|
||||
func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
ss.Extensions = low.ExtractExtensions(root)
|
||||
|
||||
@@ -70,6 +70,7 @@ x-milk: please`
|
||||
assert.Equal(t, "https://pb33f.io/openid", n.OpenIdConnectUrl.Value)
|
||||
assert.Equal(t, "please", n.FindExtension("x-milk").Value)
|
||||
assert.Equal(t, "https://pb33f.io", n.Flows.Value.Implicit.Value.TokenUrl.Value)
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ type Server struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
// GetExtensions returns all Paths extensions and satisfies the low.HasExtensions interface.
|
||||
func (s *Server) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
||||
return s.Extensions
|
||||
}
|
||||
|
||||
// FindVariable attempts to locate a ServerVariable instance using the supplied key.
|
||||
func (s *Server) FindVariable(serverVar string) *low.ValueReference[*ServerVariable] {
|
||||
return low.FindItemInMap[*ServerVariable](serverVar, s.Variables.Value)
|
||||
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
|
||||
func TestServer_Build(t *testing.T) {
|
||||
|
||||
yml := `url: https://pb33f.io
|
||||
yml := `x-coffee: hot
|
||||
url: https://pb33f.io
|
||||
description: high quality software for developers.
|
||||
variables:
|
||||
var1:
|
||||
@@ -45,6 +46,8 @@ variables:
|
||||
assert.Equal(t, "00eef99ee4a7b746be7b4ccdece59c5a96222c6206f846fafed782c9f3f9b46b",
|
||||
low.GenerateHashString(s.Value))
|
||||
|
||||
assert.Len(t, n.GetExtensions(), 1)
|
||||
|
||||
}
|
||||
|
||||
func TestServer_Build_NoVars(t *testing.T) {
|
||||
|
||||
120
document_test.go
120
document_test.go
@@ -5,6 +5,9 @@ package libopenapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel/high"
|
||||
low "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
||||
"github.com/pb33f/libopenapi/resolver"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -534,3 +537,120 @@ components:
|
||||
// Loop Point: #/components/schemas/Two
|
||||
}
|
||||
|
||||
// If you're using complex types with OpenAPI Extensions, it's simple to unpack extensions into complex
|
||||
// types using `high.UnpackExtensions()`. libopenapi retains the original raw data in the low model (not the high)
|
||||
// which means unpacking them can be a little complex.
|
||||
//
|
||||
// This example demonstrates how to use the `UnpackExtensions` with custom OpenAPI extensions.
|
||||
func ExampleNewDocument_unpacking_extensions() {
|
||||
|
||||
// define an example struct representing a cake
|
||||
type cake struct {
|
||||
Candles int
|
||||
Frosting string
|
||||
}
|
||||
|
||||
// define a struct that holds a map of cake pointers.
|
||||
type cakes struct {
|
||||
Description string
|
||||
Cakes map[string]*cake
|
||||
}
|
||||
|
||||
// define a struct representing a burger
|
||||
type burger struct {
|
||||
Sauce string
|
||||
Patty string
|
||||
}
|
||||
|
||||
// define a struct that holds a map of cake pointers
|
||||
type burgers struct {
|
||||
Description string
|
||||
Burgers map[string]*burger
|
||||
}
|
||||
|
||||
// create a specification with a schema and parameter that use complex custom cakes and burgers extensions.
|
||||
spec := `openapi: "3.1"
|
||||
components:
|
||||
schemas:
|
||||
SchemaOne:
|
||||
description: "Some schema with custom complex extensions"
|
||||
x-custom-cakes:
|
||||
description: some cakes
|
||||
cakes:
|
||||
someCake:
|
||||
candles: 10
|
||||
frosting: blue
|
||||
anotherCake:
|
||||
candles: 1
|
||||
frosting: green
|
||||
parameters:
|
||||
ParameterOne:
|
||||
description: "Some parameter also using complex extensions"
|
||||
x-custom-burgers:
|
||||
description: some burgers
|
||||
burgers:
|
||||
someBurger:
|
||||
sauce: ketchup
|
||||
patty: meat
|
||||
anotherBurger:
|
||||
sauce: mayo
|
||||
patty: lamb`
|
||||
// create a new document from specification bytes
|
||||
doc, err := NewDocument([]byte(spec))
|
||||
|
||||
// if anything went wrong, an error is thrown
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
|
||||
// build a v3 model.
|
||||
docModel, errs := doc.BuildV3Model()
|
||||
|
||||
// if anything went wrong building, indexing and resolving the model, an error is thrown
|
||||
if errs != nil {
|
||||
panic(fmt.Sprintf("cannot create new document: %e", err))
|
||||
}
|
||||
|
||||
// get a reference to SchemaOne and ParameterOne
|
||||
schemaOne := docModel.Model.Components.Schemas["SchemaOne"].Schema()
|
||||
parameterOne := docModel.Model.Components.Parameters["ParameterOne"]
|
||||
|
||||
// unpack schemaOne extensions into complex `cakes` type
|
||||
schemaOneExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *low.Schema](schemaOne)
|
||||
if schemaUnpackErrors != nil {
|
||||
panic(fmt.Sprintf("cannot unpack schema extensions: %e", err))
|
||||
}
|
||||
|
||||
// unpack parameterOne into complex `burgers` type
|
||||
parameterOneExtensions, paramUnpackErrors := high.UnpackExtensions[burgers, *v3.Parameter](parameterOne)
|
||||
if paramUnpackErrors != nil {
|
||||
panic(fmt.Sprintf("cannot unpack parameter extensions: %e", err))
|
||||
}
|
||||
|
||||
// extract extension by name for schemaOne
|
||||
customCakes := schemaOneExtensions["x-custom-cakes"]
|
||||
|
||||
// extract extension by name for schemaOne
|
||||
customBurgers := parameterOneExtensions["x-custom-burgers"]
|
||||
|
||||
// print out schemaOne complex extension details.
|
||||
fmt.Printf("schemaOne 'x-custom-cakes' (%s) has %d cakes, 'someCake' has %d candles and %s frosting\n",
|
||||
customCakes.Description,
|
||||
len(customCakes.Cakes),
|
||||
customCakes.Cakes["someCake"].Candles,
|
||||
customCakes.Cakes["someCake"].Frosting,
|
||||
)
|
||||
|
||||
// print out parameterOne complex extension details.
|
||||
fmt.Printf("parameterOne 'x-custom-burgers' (%s) has %d burgers, 'anotherBurger' has %s sauce and a %s patty\n",
|
||||
customBurgers.Description,
|
||||
len(customBurgers.Burgers),
|
||||
customBurgers.Burgers["anotherBurger"].Sauce,
|
||||
customBurgers.Burgers["anotherBurger"].Patty,
|
||||
)
|
||||
|
||||
// Output: schemaOne 'x-custom-cakes' (some cakes) has 2 cakes, 'someCake' has 10 candles and blue frosting
|
||||
//parameterOne 'x-custom-burgers' (some burgers) has 2 burgers, 'anotherBurger' has mayo sauce and a lamb patty
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user