Handling extensions using libopenapi
Using OpenAPI vendor extensions, both simple and complexOpenAPI extensions (also known as vendor extensions) are
properties with a prefix of x-
that can be added to many OpenAPI objects throughout the OpenAPI specification.
Both high-level and low-level models support extensions and are available on both
via the Extensions
property available on all models that support it.
High level models
It’s pretty simple, all high-level models have the signature:
type SomeHighLevelOpenAPIObject struct {
...
Extensions map[string]any // <-- all compatible high level
// objects have this extension
// signature.
}
Low level models
All low-level models have the signature:
type SomeLowLevelOpenAPIObject struct {
...
// all compatible low-level objects have this extension signature.
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
Simple extensions
If the specification uses a simple/primitive extension like string
or int
then, just cast to that type and
you’re on your way.
Complex Extensions
Let’s say the extension x-custom-cakes
is an object, that looks something like this:
openapi: '3.1'
components:
schemas:
MySchema:
description: "Some schema with custom complex extensions"
x-custom-cakes:
description: some cakes
cakes:
someCake:
candles: 10
frosting: blue
someStrangeVarName: mapping is required to extract these.
anotherCake:
candles: 1
frosting: green
The object that represents x-custom-cakes
looks like this:
// define an example struct representing a cake
// super important to remember to use hints/meta-data to map properties correctly.
type cake struct {
Candles int `yaml:"candles"`
Frosting string `yaml:"frosting"`
Some_Strange_Var_Name string `yaml:"someStrangeVarName"`
}
// define a struct that holds a map of cake pointers.
type cakes struct {
Description string
Cakes map[string]*cake
}
When we’re reading the high-level models and we want to unpack
our extensions into these complex structs from a type of map[string]any
?
UnpackExtensions
There is a method to make this simple available in the high
package within the datamodel
package.
It’s called UnpackExtensions
It’s a generic function that requires the custom type and the low-level model type of the object that contains the extensions you’d like to unpack.
Here is an example from our cake spec.
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"
)
type cake struct {
Candles int `yaml:"candles"`
Frosting string `yaml:"frosting"`
Some_Strange_Var_Name string `yaml:"someStrangeVarName"`
}
type cakes struct {
Description string
Cakes map[string]*cake
}
func main() {
// create a specification with a schema and parameter that use complex
// custom cakes and burgers extensions.
spec := `
openapi: "3.1"
components:
schemas:
MySchema:
description: "Some schema with custom complex extensions"
x-custom-cakes:
description: some cakes
cakes:
someCake:
candles: 10
frosting: blue
someStrangeVarName: mapping is required to extract these.
anotherCake:
candles: 1
frosting: green
`
// create a new document from specification bytes, ignore errors
doc, _ := libopenapi.NewDocument([]byte(spec))
// build a v3 model, ignore errors
docModel, _ := doc.BuildV3Model()
// get a reference to MySchema
mySchema := docModel.Model.Components.Schemas["MySchema"].Schema()
// unpack mySchema extensions into complex `cakes` type
mySchemaExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *lowbase.Schema](mySchema)
if schemaUnpackErrors != nil {
panic(fmt.Sprintf("cannot unpack schema extensions: %e", err))
}
// extract extension by name for mySchema
customCakes := mySchemaExtensions["x-custom-cakes"]
// print out mySchema complex extension details.
fmt.Printf("mySchema '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,
)
}
This will output: