niki/vendor/github.com/sv-tools/openapi/spec/extensions.go

139 lines
3.8 KiB
Go

package spec
import (
"encoding/json"
"fmt"
"strings"
"gopkg.in/yaml.v3"
)
const ExtensionPrefix = "x-"
// Extendable allows extensions to the OpenAPI Schema.
// The field name MUST begin with `x-`, for example, `x-internal-id`.
// Field names beginning `x-oai-` and `x-oas-` are reserved for uses defined by the OpenAPI Initiative.
// The value can be null, a primitive, an array or an object.
//
// https://spec.openapis.org/oas/v3.1.0#specification-extensions
//
// Example:
//
// openapi: 3.1.0
// info:
// title: Sample Pet Store App
// summary: A pet store manager.
// description: This is a sample server for a pet store.
// version: 1.0.1
// x-build-data: 2006-01-02T15:04:05Z07:00
// x-build-commit-id: dac33af14d0d4a5f1c226141042ca7cefc6aeb75
type Extendable[T any] struct {
Spec *T `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"`
}
// NewExtendable creates new Extendable object for given spec
func NewExtendable[T any](spec *T) *Extendable[T] {
ext := Extendable[T]{
Spec: spec,
Extensions: make(map[string]any),
}
return &ext
}
// MarshalJSON implements json.Marshaler interface.
func (o *Extendable[T]) MarshalJSON() ([]byte, error) {
var raw map[string]json.RawMessage
exts, err := json.Marshal(&o.Extensions)
if err != nil {
return nil, fmt.Errorf("%T.Extensions: %w", o.Spec, err)
}
if err := json.Unmarshal(exts, &raw); err != nil {
return nil, fmt.Errorf("%T(raw extensions): %w", o.Spec, err)
}
fields, err := json.Marshal(&o.Spec)
if err != nil {
return nil, fmt.Errorf("%T: %w", o.Spec, err)
}
if err := json.Unmarshal(fields, &raw); err != nil {
return nil, fmt.Errorf("%T(raw fields): %w", o.Spec, err)
}
data, err := json.Marshal(&raw)
if err != nil {
return nil, fmt.Errorf("%T(raw): %w", o.Spec, err)
}
return data, nil
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (o *Extendable[T]) UnmarshalJSON(data []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return fmt.Errorf("%T: %w", o.Spec, err)
}
o.Extensions = make(map[string]any)
for name, value := range raw {
if strings.HasPrefix(name, ExtensionPrefix) {
var v any
if err := json.Unmarshal(value, &v); err != nil {
return fmt.Errorf("%T.Extensions.%s: %w", o.Spec, name, err)
}
o.Extensions[name] = v
delete(raw, name)
}
}
fields, err := json.Marshal(&raw)
if err != nil {
return fmt.Errorf("%T(raw): %w", o.Spec, err)
}
if err := json.Unmarshal(fields, &o.Spec); err != nil {
return fmt.Errorf("%T: %w", o.Spec, err)
}
return nil
}
// MarshalYAML implements yaml.Marshaler interface.
func (o *Extendable[T]) MarshalYAML() (any, error) {
var raw map[string]any
exts, err := yaml.Marshal(&o.Extensions)
if err != nil {
return nil, fmt.Errorf("%T.Extensions: %w", o.Spec, err)
}
if err := yaml.Unmarshal(exts, &raw); err != nil {
return nil, fmt.Errorf("%T(raw extensions): %w", o.Spec, err)
}
fields, err := yaml.Marshal(&o.Spec)
if err != nil {
return nil, fmt.Errorf("%T: %w", o.Spec, err)
}
if err := yaml.Unmarshal(fields, &raw); err != nil {
return nil, fmt.Errorf("%T(raw fields): %w", o.Spec, err)
}
return raw, nil
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (o *Extendable[T]) UnmarshalYAML(node *yaml.Node) error {
var raw map[string]any
if err := node.Decode(&raw); err != nil {
return fmt.Errorf("%T: %w", o.Spec, err)
}
o.Extensions = make(map[string]any)
for name, value := range raw {
if strings.HasPrefix(name, ExtensionPrefix) {
o.Extensions[name] = value
delete(raw, name)
}
}
fields, err := yaml.Marshal(&raw)
if err != nil {
return fmt.Errorf("%T(raw): %w", o.Spec, err)
}
if err := yaml.Unmarshal(fields, &o.Spec); err != nil {
return fmt.Errorf("%T: %w", o.Spec, err)
}
return nil
}