fix: add involves edge from task to agent:nomos at creation
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
2026-08-11 22:03:12 +02:00
parent 7d6a3320d4
commit febc153b7f
3384 changed files with 945212 additions and 2 deletions

21
vendor/github.com/getkin/kin-openapi/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2018 the project authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,55 @@
package openapi3
import (
"context"
)
// Callback is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
type Callback struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
m map[string]*PathItem
}
// NewCallback builds a Callback object with path items in insertion order.
func NewCallback(opts ...NewCallbackOption) *Callback {
Callback := NewCallbackWithCapacity(len(opts))
for _, opt := range opts {
opt(Callback)
}
return Callback
}
// NewCallbackOption describes options to NewCallback func
type NewCallbackOption func(*Callback)
// WithCallback adds Callback as an option to NewCallback
func WithCallback(cb string, pathItem *PathItem) NewCallbackOption {
return func(callback *Callback) {
if p := pathItem; p != nil && cb != "" {
callback.Set(cb, p)
}
}
}
// Validate returns an error if Callback does not comply with the OpenAPI spec.
func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, key := range callback.Keys() {
v := callback.Value(key)
if err := v.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, callback.Extensions, callback.Origin)
}
// UnmarshalJSON sets Callbacks to a copy of data.
func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) {
*callbacks, err = unmarshalStringMapP[CallbackRef](data)
return
}

View File

@@ -0,0 +1,308 @@
package openapi3
import (
"context"
"encoding/json"
"fmt"
"maps"
"github.com/go-openapi/jsonpointer"
)
type Callbacks map[string]*CallbackRef // Callbacks represents components' named callbacks
type Examples map[string]*ExampleRef // Examples represents components' named examples
type Headers map[string]*HeaderRef // Headers represents components' named headers
type Links map[string]*LinkRef // Links represents components' named links
type ParametersMap map[string]*ParameterRef // ParametersMap represents components' named parameters
type RequestBodies map[string]*RequestBodyRef // RequestBodies represents components' named request bodies
type ResponseBodies map[string]*ResponseRef // ResponseBodies represents components' named response bodies
type Schemas map[string]*SchemaRef // Schemas represents components' named schemas
type SecuritySchemes map[string]*SecuritySchemeRef // SecuritySchemes represents components' named security schemes
// Components is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
type Components struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Responses ResponseBodies `json:"responses,omitempty" yaml:"responses,omitempty"`
SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
}
func NewComponents() Components {
return Components{}
}
// MarshalJSON returns the JSON encoding of Components.
func (components Components) MarshalJSON() ([]byte, error) {
x, err := components.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Components.
func (components Components) MarshalYAML() (any, error) {
m := make(map[string]any, 9+len(components.Extensions))
maps.Copy(m, components.Extensions)
if x := components.Schemas; len(x) != 0 {
m["schemas"] = x
}
if x := components.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := components.Headers; len(x) != 0 {
m["headers"] = x
}
if x := components.RequestBodies; len(x) != 0 {
m["requestBodies"] = x
}
if x := components.Responses; len(x) != 0 {
m["responses"] = x
}
if x := components.SecuritySchemes; len(x) != 0 {
m["securitySchemes"] = x
}
if x := components.Examples; len(x) != 0 {
m["examples"] = x
}
if x := components.Links; len(x) != 0 {
m["links"] = x
}
if x := components.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
return m, nil
}
// UnmarshalJSON sets Components to a copy of data.
func (components *Components) UnmarshalJSON(data []byte) error {
type ComponentsBis Components
var x ComponentsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "schemas")
delete(x.Extensions, "parameters")
delete(x.Extensions, "headers")
delete(x.Extensions, "requestBodies")
delete(x.Extensions, "responses")
delete(x.Extensions, "securitySchemes")
delete(x.Extensions, "examples")
delete(x.Extensions, "links")
delete(x.Extensions, "callbacks")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*components = Components(x)
return nil
}
// Validate returns an error if Components does not comply with the OpenAPI spec.
func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
validateMap := func(label string, names []string, validate func(k string) error) error {
for _, k := range names {
if idErr := ValidateIdentifier(k); idErr != nil {
if err := me.emit(&ComponentValidationError{Section: label, Name: k, Cause: idErr}); err != nil {
return err
}
// Skip validating the component's value when its name is
// invalid: any leaf error from validate(k) would surface as
// "<bad-name>: <leaf-error>" and has no resolution path
// until the name is fixed. The continue keeps the noise
// per component bounded to a single, actionable finding.
continue
}
wrap := func(e error) error { return &ComponentValidationError{Section: label, Name: k, Cause: e} }
if err := me.emitWrapped(wrap, validate(k)); err != nil {
return err
}
}
return nil
}
if err := validateMap("schema", componentNames(components.Schemas), func(k string) error {
return components.Schemas[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("parameter", componentNames(components.Parameters), func(k string) error {
return components.Parameters[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("request body", componentNames(components.RequestBodies), func(k string) error {
return components.RequestBodies[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("response", componentNames(components.Responses), func(k string) error {
return components.Responses[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("header", componentNames(components.Headers), func(k string) error {
return components.Headers[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("security scheme", componentNames(components.SecuritySchemes), func(k string) error {
return components.SecuritySchemes[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("example", componentNames(components.Examples), func(k string) error {
return components.Examples[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("link", componentNames(components.Links), func(k string) error {
return components.Links[k].Validate(ctx)
}); err != nil {
return err
}
if err := validateMap("callback", componentNames(components.Callbacks), func(k string) error {
return components.Callbacks[k].Validate(ctx)
}); err != nil {
return err
}
return me.finalize(validateExtensions(ctx, components.Extensions, components.Origin))
}
var _ jsonpointer.JSONPointable = (*Schemas)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Schemas) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no schema %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m ParametersMap) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no parameter %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Headers)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Headers) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no header %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m RequestBodies) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no request body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m ResponseBodies) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no response body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m SecuritySchemes) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no security scheme body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Examples)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Examples) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no example body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Links)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Links) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no link body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Callbacks) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no callback body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}

View File

@@ -0,0 +1,69 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Contact is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
type Contact struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Email string `json:"email,omitempty" yaml:"email,omitempty"`
}
// MarshalJSON returns the JSON encoding of Contact.
func (contact Contact) MarshalJSON() ([]byte, error) {
x, err := contact.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Contact.
func (contact Contact) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(contact.Extensions))
maps.Copy(m, contact.Extensions)
if x := contact.Name; x != "" {
m["name"] = x
}
if x := contact.URL; x != "" {
m["url"] = x
}
if x := contact.Email; x != "" {
m["email"] = x
}
return m, nil
}
// UnmarshalJSON sets Contact to a copy of data.
func (contact *Contact) UnmarshalJSON(data []byte) error {
type ContactBis Contact
var x ContactBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "url")
delete(x.Extensions, "email")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*contact = Contact(x)
return nil
}
// Validate returns an error if Contact does not comply with the OpenAPI spec.
func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, contact.Extensions, contact.Origin)
}

View File

@@ -0,0 +1,123 @@
package openapi3
import (
"context"
"strings"
)
// Content is specified by OpenAPI/Swagger 3.0 standard.
type Content map[string]*MediaType
func NewContent() Content {
return make(Content)
}
func NewContentWithSchema(schema *Schema, consumes []string) Content {
if len(consumes) == 0 {
return Content{
"*/*": NewMediaType().WithSchema(schema),
}
}
content := make(map[string]*MediaType, len(consumes))
for _, mediaType := range consumes {
content[mediaType] = NewMediaType().WithSchema(schema)
}
return content
}
func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content {
if len(consumes) == 0 {
return Content{
"*/*": NewMediaType().WithSchemaRef(schema),
}
}
content := make(map[string]*MediaType, len(consumes))
for _, mediaType := range consumes {
content[mediaType] = NewMediaType().WithSchemaRef(schema)
}
return content
}
func NewContentWithJSONSchema(schema *Schema) Content {
return Content{
"application/json": NewMediaType().WithSchema(schema),
}
}
func NewContentWithJSONSchemaRef(schema *SchemaRef) Content {
return Content{
"application/json": NewMediaType().WithSchemaRef(schema),
}
}
func NewContentWithFormDataSchema(schema *Schema) Content {
return Content{
"multipart/form-data": NewMediaType().WithSchema(schema),
}
}
func NewContentWithFormDataSchemaRef(schema *SchemaRef) Content {
return Content{
"multipart/form-data": NewMediaType().WithSchemaRef(schema),
}
}
func (content Content) Get(mime string) *MediaType {
// If the mime is empty then short-circuit to the wildcard.
// We do this here so that we catch only the specific case of
// and empty mime rather than a present, but invalid, mime type.
if mime == "" {
return content["*/*"]
}
// Start by making the most specific match possible
// by using the mime type in full.
if v := content[mime]; v != nil {
return v
}
// If an exact match is not found then we strip all
// metadata from the mime type and only use the x/y
// portion.
i := strings.IndexByte(mime, ';')
if i < 0 {
// If there is no metadata then preserve the full mime type
// string for later wildcard searches.
i = len(mime)
}
mime = mime[:i]
if v := content[mime]; v != nil {
return v
}
// If the x/y pattern has no specific match then we
// try the x/* pattern.
i = strings.IndexByte(mime, '/')
if i < 0 {
// In the case that the given mime type is not valid because it is
// missing the subtype we return nil so that this does not accidentally
// resolve with the wildcard.
return nil
}
mime = mime[:i] + "/*"
if v := content[mime]; v != nil {
return v
}
// Finally, the most generic match of */* is returned
// as a catch-all.
return content["*/*"]
}
// Validate returns an error if Content does not comply with the OpenAPI spec.
func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, k := range componentNames(content) {
if err := content[k].Validate(ctx); err != nil {
return err
}
}
return nil
}
// UnmarshalJSON sets Content to a copy of data.
func (content *Content) UnmarshalJSON(data []byte) (err error) {
*content, err = unmarshalStringMapP[MediaType](data)
return
}

View File

@@ -0,0 +1,76 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Discriminator is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
type Discriminator struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
PropertyName string `json:"propertyName" yaml:"propertyName"` // required
Mapping StringMap[MappingRef] `json:"mapping,omitempty" yaml:"mapping,omitempty"`
}
// MappingRef is a ref to a Schema objects. Unlike SchemaRefs it is serialised
// as a plain string instead of an object with a $ref key, as such it also does
// not support extensions.
type MappingRef SchemaRef
func (mr *MappingRef) UnmarshalText(data []byte) error {
mr.Ref = string(data)
return nil
}
func (mr MappingRef) MarshalText() ([]byte, error) {
return []byte(mr.Ref), nil
}
// MarshalJSON returns the JSON encoding of Discriminator.
func (discriminator Discriminator) MarshalJSON() ([]byte, error) {
x, err := discriminator.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Discriminator.
func (discriminator Discriminator) MarshalYAML() (any, error) {
m := make(map[string]any, 2+len(discriminator.Extensions))
maps.Copy(m, discriminator.Extensions)
m["propertyName"] = discriminator.PropertyName
if x := discriminator.Mapping; len(x) != 0 {
m["mapping"] = x
}
return m, nil
}
// UnmarshalJSON sets Discriminator to a copy of data.
func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
type DiscriminatorBis Discriminator
var x DiscriminatorBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "propertyName")
delete(x.Extensions, "mapping")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*discriminator = Discriminator(x)
return nil
}
// Validate returns an error if Discriminator does not comply with the OpenAPI spec.
func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, discriminator.Extensions, discriminator.Origin)
}

25
vendor/github.com/getkin/kin-openapi/openapi3/doc.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
// Package openapi3 parses and writes OpenAPI 3 specification documents.
//
// Supports both OpenAPI 3.0 and OpenAPI 3.1:
// - OpenAPI 3.0.x: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md
// - OpenAPI 3.1.x: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md
//
// OpenAPI 3.1 Features:
// - Type arrays with null support (e.g., ["string", "null"])
// - JSON Schema 2020-12 keywords (const, examples, prefixItems, etc.)
// - Webhooks for defining callback operations
// - JSON Schema dialect specification
// - SPDX license identifiers
//
// The implementation maintains 100% backward compatibility with OpenAPI 3.0.
//
// For OpenAPI 3.1 validation, use the JSON Schema 2020-12 validator option:
//
// schema.VisitJSON(value, openapi3.EnableJSONSchema2020())
//
// Version detection is available via helper methods:
//
// if doc.IsOpenAPI31OrLater() {
// // Handle OpenAPI 3.1 specific features
// }
package openapi3

View File

@@ -0,0 +1,151 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Encoding is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
type Encoding struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
}
func NewEncoding() *Encoding {
return &Encoding{}
}
// Encodings is a map of encoding objects keyed by field name.
type Encodings map[string]*Encoding
// UnmarshalJSON sets Encodings to a copy of data.
func (encodings *Encodings) UnmarshalJSON(data []byte) (err error) {
*encodings, err = unmarshalStringMapP[Encoding](data)
return
}
func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding {
return encoding.WithHeaderRef(name, &HeaderRef{
Value: header,
})
}
func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding {
headers := encoding.Headers
if headers == nil {
headers = make(map[string]*HeaderRef)
encoding.Headers = headers
}
headers[name] = ref
return encoding
}
// MarshalJSON returns the JSON encoding of Encoding.
func (encoding Encoding) MarshalJSON() ([]byte, error) {
x, err := encoding.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Encoding.
func (encoding Encoding) MarshalYAML() (any, error) {
m := make(map[string]any, 5+len(encoding.Extensions))
maps.Copy(m, encoding.Extensions)
if x := encoding.ContentType; x != "" {
m["contentType"] = x
}
if x := encoding.Headers; len(x) != 0 {
m["headers"] = x
}
if x := encoding.Style; x != "" {
m["style"] = x
}
if x := encoding.Explode; x != nil {
m["explode"] = x
}
if x := encoding.AllowReserved; x {
m["allowReserved"] = x
}
return m, nil
}
// UnmarshalJSON sets Encoding to a copy of data.
func (encoding *Encoding) UnmarshalJSON(data []byte) error {
type EncodingBis Encoding
var x EncodingBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "contentType")
delete(x.Extensions, "headers")
delete(x.Extensions, "style")
delete(x.Extensions, "explode")
delete(x.Extensions, "allowReserved")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*encoding = Encoding(x)
return nil
}
// SerializationMethod returns a serialization method of request body.
// When serialization method is not defined the method returns the default serialization method.
func (encoding *Encoding) SerializationMethod() *SerializationMethod {
sm := &SerializationMethod{Style: SerializationForm, Explode: true}
if encoding != nil {
if encoding.Style != "" {
sm.Style = encoding.Style
}
if encoding.Explode != nil {
sm.Explode = *encoding.Explode
}
}
return sm
}
// Validate returns an error if Encoding does not comply with the OpenAPI spec.
func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if encoding == nil {
return nil
}
for _, k := range componentNames(encoding.Headers) {
v := encoding.Headers[k]
if err := ValidateIdentifier(k); err != nil {
return nil
}
if err := v.Validate(ctx); err != nil {
return nil
}
}
// Validate a media types's serialization method.
sm := encoding.SerializationMethod()
switch {
case sm.Style == SerializationForm && sm.Explode,
sm.Style == SerializationForm && !sm.Explode,
sm.Style == SerializationSpaceDelimited && sm.Explode,
sm.Style == SerializationSpaceDelimited && !sm.Explode,
sm.Style == SerializationPipeDelimited && sm.Explode,
sm.Style == SerializationPipeDelimited && !sm.Explode,
sm.Style == SerializationDeepObject && sm.Explode:
default:
return newInvalidSerializationMethod("media type", sm.Style, sm.Explode, encoding.Origin)
}
return validateExtensions(ctx, encoding.Extensions, encoding.Origin)
}

View File

@@ -0,0 +1,82 @@
package openapi3
import "context"
// errCollector aggregates validation errors inside a Validate method.
//
// When multi-error mode is enabled (EnableMultiError), emit records the error
// and returns nil so the caller continues to the next sibling; if the error is
// itself a MultiError, its leaves are appended individually so the result is a
// flat MultiError of fully-wrapped problems (this matches what most consumers
// expect: one MultiError entry per independent problem).
//
// When multi-error mode is off, emit returns the error unchanged so the caller
// fails fast, preserving the historical behavior byte-for-byte.
//
// emitWrapped applies wrap to err, distributing wrap over each leaf when err
// is a MultiError. This is how validators attach per-section / per-path /
// per-operation context to each aggregated leaf.
//
// result returns the accumulated MultiError, or nil if none were recorded.
type errCollector struct {
multi bool
errs MultiError
}
func newErrCollector(ctx context.Context) *errCollector {
return &errCollector{multi: getValidationOptions(ctx).multiErrorEnabled}
}
func (c *errCollector) emit(err error) error {
if err == nil {
return nil
}
if !c.multi {
return err
}
if me, ok := err.(MultiError); ok {
for _, sub := range me {
if e := c.emit(sub); e != nil {
return e
}
}
return nil
}
c.errs = append(c.errs, err)
return nil
}
func (c *errCollector) emitWrapped(wrap func(error) error, err error) error {
if err == nil {
return nil
}
if !c.multi {
return wrap(err)
}
if me, ok := err.(MultiError); ok {
for _, sub := range me {
if e := c.emitWrapped(wrap, sub); e != nil {
return e
}
}
return nil
}
return c.emit(wrap(err))
}
func (c *errCollector) result() error {
if len(c.errs) > 0 {
return c.errs
}
return nil
}
// finalize emits err (typically the last sibling validation in a container,
// e.g. the extensions check) and returns the accumulated result. It collapses
// the trailing emit-then-result pattern into a single line at each call site.
func (c *errCollector) finalize(err error) error {
if e := c.emit(err); e != nil {
return e
}
return c.result()
}

View File

@@ -0,0 +1,69 @@
package openapi3
import (
"bytes"
"errors"
)
// MultiError is a collection of errors, intended for when
// multiple issues need to be reported upstream
type MultiError []error
func (me MultiError) Error() string {
return spliceErr(" | ", me)
}
func spliceErr(sep string, errs []error) string {
buff := &bytes.Buffer{}
for i, e := range errs {
buff.WriteString(e.Error())
if i != len(errs)-1 {
buff.WriteString(sep)
}
}
return buff.String()
}
// Is allows you to determine if a generic error is in fact a MultiError using `errors.Is()`
// It will also return true if any of the contained errors match target
func (me MultiError) Is(target error) bool {
if _, ok := target.(MultiError); ok {
return true
}
for _, e := range me {
if errors.Is(e, target) {
return true
}
}
return false
}
// As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type
func (me MultiError) As(target any) bool {
for _, e := range me {
if errors.As(e, target) {
return true
}
}
return false
}
type multiErrorForOneOf MultiError
func (meo multiErrorForOneOf) Error() string {
return spliceErr(" Or ", meo)
}
func (meo multiErrorForOneOf) Unwrap() error {
return MultiError(meo)
}
type multiErrorForAllOf MultiError
func (mea multiErrorForAllOf) Error() string {
return spliceErr(" And ", mea)
}
func (mea multiErrorForAllOf) Unwrap() error {
return MultiError(mea)
}

View File

@@ -0,0 +1,90 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Example is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object
type Example struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value any `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
}
func NewExample(value any) *Example {
return &Example{Value: value}
}
// MarshalJSON returns the JSON encoding of Example.
func (example Example) MarshalJSON() ([]byte, error) {
x, err := example.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Example.
func (example Example) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(example.Extensions))
maps.Copy(m, example.Extensions)
if x := example.Summary; x != "" {
m["summary"] = x
}
if x := example.Description; x != "" {
m["description"] = x
}
if x := example.Value; x != nil {
m["value"] = x
}
if x := example.ExternalValue; x != "" {
m["externalValue"] = x
}
return m, nil
}
// UnmarshalJSON sets Example to a copy of data.
func (example *Example) UnmarshalJSON(data []byte) error {
type ExampleBis Example
var x ExampleBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "value")
delete(x.Extensions, "externalValue")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*example = Example(x)
return nil
}
// Validate returns an error if Example does not comply with the OpenAPI spec.
func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if example.Value != nil && example.ExternalValue != "" {
return newExampleValueExternalValueExclusive(example.Origin)
}
if example.Value == nil && example.ExternalValue == "" {
return newExampleValueOrExternalValueRequired(example.Origin)
}
return validateExtensions(ctx, example.Extensions, example.Origin)
}
// UnmarshalJSON sets Examples to a copy of data.
func (examples *Examples) UnmarshalJSON(data []byte) (err error) {
*examples, err = unmarshalStringMapP[ExampleRef](data)
return
}

View File

@@ -0,0 +1,20 @@
package openapi3
import "context"
func validateExampleValue(ctx context.Context, input any, schema *Schema) error {
opts := []SchemaValidationOption{MultiErrors()}
vo := getValidationOptions(ctx)
if vo.examplesValidationAsReq {
opts = append(opts, VisitAsRequest())
} else if vo.examplesValidationAsRes {
opts = append(opts, VisitAsResponse())
}
if vo.jsonSchema2020ValidationEnabled {
opts = append(opts, EnableJSONSchema2020())
}
return schema.VisitJSON(input, opts...)
}

View File

@@ -0,0 +1,36 @@
package openapi3
import (
"context"
"strings"
)
// validateExtensions reports any non-`x-` keys in the given extensions
// map that are not explicitly allowed by the validation context. The
// origin argument is attached to the resulting ExtraSiblingFieldsError
// so callers can pin the finding to the parent object that carries the
// unknown keys; pass nil when the parent has no Origin (the loader was
// run with IncludeOrigin = false, or the parent was constructed
// programmatically without an Origin set).
func validateExtensions(ctx context.Context, extensions map[string]any, origin *Origin) error { // FIXME: newtype + Validate(...)
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
var unknowns []string
for _, k := range componentNames(extensions) {
if strings.HasPrefix(k, "x-") {
continue
}
if allowed != nil {
if _, ok := allowed[k]; ok {
continue
}
}
unknowns = append(unknowns, k)
}
if len(unknowns) != 0 {
return newExtraSiblingFields(unknowns, origin)
}
return nil
}

View File

@@ -0,0 +1,76 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
"net/url"
)
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
type ExternalDocs struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
// MarshalJSON returns the JSON encoding of ExternalDocs.
func (e ExternalDocs) MarshalJSON() ([]byte, error) {
x, err := e.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of ExternalDocs.
func (e ExternalDocs) MarshalYAML() (any, error) {
m := make(map[string]any, 2+len(e.Extensions))
maps.Copy(m, e.Extensions)
if x := e.Description; x != "" {
m["description"] = x
}
if x := e.URL; x != "" {
m["url"] = x
}
return m, nil
}
// UnmarshalJSON sets ExternalDocs to a copy of data.
func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
type ExternalDocsBis ExternalDocs
var x ExternalDocsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "description")
delete(x.Extensions, "url")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*e = ExternalDocs(x)
return nil
}
// Validate returns an error if ExternalDocs does not comply with the OpenAPI spec.
func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if e.URL == "" {
if err := me.emit(newExternalDocsURLRequired(e.Origin)); err != nil {
return err
}
}
if _, err := url.Parse(e.URL); err != nil {
if err := me.emit(&ExternalDocsURLValidationError{Cause: err}); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, e.Extensions, e.Origin))
}

100
vendor/github.com/getkin/kin-openapi/openapi3/header.go generated vendored Normal file
View File

@@ -0,0 +1,100 @@
package openapi3
import (
"context"
"github.com/go-openapi/jsonpointer"
)
// Header is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object
type Header struct {
Parameter
}
var _ jsonpointer.JSONPointable = (*Header)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (header Header) JSONLookup(token string) (any, error) {
return header.Parameter.JSONLookup(token)
}
// MarshalJSON returns the JSON encoding of Header.
func (header Header) MarshalJSON() ([]byte, error) {
return header.Parameter.MarshalJSON()
}
// UnmarshalJSON sets Header to a copy of data.
func (header *Header) UnmarshalJSON(data []byte) error {
return header.Parameter.UnmarshalJSON(data)
}
// MarshalYAML returns the JSON encoding of Header.
func (header Header) MarshalYAML() (any, error) {
return header.Parameter, nil
}
// SerializationMethod returns a header's serialization method.
func (header *Header) SerializationMethod() (*SerializationMethod, error) {
style := header.Style
if style == "" {
style = SerializationSimple
}
explode := false
if header.Explode != nil {
explode = *header.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
}
// Validate returns an error if Header does not comply with the OpenAPI spec.
func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if header.Name != "" {
return newHeaderNameForbidden(header.Origin)
}
if header.In != "" {
return newHeaderInForbidden(header.Origin)
}
// Validate a parameter's serialization method.
sm, err := header.SerializationMethod()
if err != nil {
return err
}
if smSupported := false ||
sm.Style == SerializationSimple && !sm.Explode ||
sm.Style == SerializationSimple && sm.Explode; !smSupported {
e := newInvalidSerializationMethod("header", sm.Style, sm.Explode, header.Origin)
return &HeaderFieldValidationError{Field: "schema", Cause: e}
}
if (header.Schema == nil) == (len(header.Content) == 0) {
return &HeaderFieldValidationError{Field: "schema",
Cause: newHeaderContentSchemaExactlyOne(header, header.Origin)}
}
if schema := header.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return &HeaderFieldValidationError{Field: "schema", Cause: err}
}
}
if content := header.Content; content != nil {
if len(content) > 1 {
return &HeaderFieldValidationError{Field: "content",
Cause: newHeaderContentSingleEntry(header.Origin)}
}
if err := content.Validate(ctx); err != nil {
return &HeaderFieldValidationError{Field: "content", Cause: err}
}
}
return nil
}
// UnmarshalJSON sets Headers to a copy of data.
func (headers *Headers) UnmarshalJSON(data []byte) (err error) {
*headers, err = unmarshalStringMapP[HeaderRef](data)
return
}

View File

@@ -0,0 +1,275 @@
package openapi3
import (
"fmt"
"net/url"
"path"
"reflect"
"regexp"
"slices"
"strings"
"github.com/go-openapi/jsonpointer"
)
const identifierChars = `a-zA-Z0-9._-`
// IdentifierRegExp verifies whether Component object key matches contains just 'identifierChars', according to OpenAPI v3.x.
// InvalidIdentifierCharRegExp matches all characters not contained in 'identifierChars'.
// However, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in order not to fail
// converted v2-v3 validation
var (
IdentifierRegExp = regexp.MustCompile(`^[` + identifierChars + `]+$`)
InvalidIdentifierCharRegExp = regexp.MustCompile(`[^` + identifierChars + `]`)
)
// ValidateIdentifier returns an error if the given component name does not match [IdentifierRegExp].
func ValidateIdentifier(value string) error {
if IdentifierRegExp.MatchString(value) {
return nil
}
return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (charset: [%q])", value, identifierChars)
}
// Ptr is a helper for defining OpenAPI schemas.
func Ptr[T any](value T) *T {
return &value
}
// Float64Ptr is a helper for defining OpenAPI schemas.
//
// Deprecated: Use Ptr instead.
func Float64Ptr(value float64) *float64 {
return &value
}
// BoolPtr is a helper for defining OpenAPI schemas.
//
// Deprecated: Use Ptr instead.
func BoolPtr(value bool) *bool {
return &value
}
// Int64Ptr is a helper for defining OpenAPI schemas.
//
// Deprecated: Use Ptr instead.
func Int64Ptr(value int64) *int64 {
return &value
}
// Uint64Ptr is a helper for defining OpenAPI schemas.
//
// Deprecated: Use Ptr instead.
func Uint64Ptr(value uint64) *uint64 {
return &value
}
// componentNames returns the map keys in a sorted slice.
func componentNames[E any](s map[string]E) []string {
out := make([]string, 0, len(s))
for i := range s {
out = append(out, i)
}
slices.Sort(out)
return out
}
// copyURI makes a copy of the pointer.
func copyURI(u *url.URL) *url.URL {
if u == nil {
return nil
}
c := *u // shallow-copy
return &c
}
type ComponentRef interface {
RefString() string
RefPath() *url.URL
CollectionName() string
}
// refersToSameDocument returns if the $ref refers to the same document.
//
// Documents in different directories will have distinct $ref values that resolve to
// the same document.
// For example, consider the 3 files:
//
// /records.yaml
// /root.yaml $ref: records.yaml
// /schema/other.yaml $ref: ../records.yaml
//
// The records.yaml reference in the 2 latter refers to the same document.
func refersToSameDocument(o1 ComponentRef, o2 ComponentRef) bool {
if o1 == nil || o2 == nil {
return false
}
r1 := o1.RefPath()
r2 := o2.RefPath()
if r1 == nil || r2 == nil {
return false
}
// refURL is relative to the working directory & base spec file.
return referenceURIMatch(r1, r2)
}
// referencesRootDocument returns if the $ref points to the root document of the OpenAPI spec.
//
// If the document has no location, perhaps loaded from data in memory, it always returns false.
func referencesRootDocument(doc *T, ref ComponentRef) bool {
if doc.url == nil || ref == nil || ref.RefPath() == nil {
return false
}
refURL := *ref.RefPath()
refURL.Fragment = ""
// Check referenced element was in the root document.
return referenceURIMatch(doc.url, &refURL)
}
func referenceURIMatch(u1 *url.URL, u2 *url.URL) bool {
s1, s2 := *u1, *u2
if s1.Scheme == "" {
s1.Scheme = "file"
}
if s2.Scheme == "" {
s2.Scheme = "file"
}
return s1.String() == s2.String()
}
// ReferencesComponentInRootDocument returns if the given component reference references
// the same document or element as another component reference in the root document's
// '#/components/<type>'. If it does, it returns the name of it in the form
// '#/components/<type>/NameXXX'
//
// Of course given a component from the root document will always match itself.
//
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls
//
// Example. Take the spec with directory structure:
//
// openapi.yaml
// schemas/
// ├─ record.yaml
// ├─ records.yaml
//
// In openapi.yaml we have:
//
// components:
// schemas:
// Record:
// $ref: schemas/record.yaml
//
// Case 1: records.yml references a component in the root document
//
// $ref: ../openapi.yaml#/components/schemas/Record
//
// This would return...
//
// #/components/schemas/Record
//
// Case 2: records.yml indirectly refers to the same schema
// as a schema the root document's '#/components/schemas'.
//
// $ref: ./record.yaml
//
// This would also return...
//
// #/components/schemas/Record
func ReferencesComponentInRootDocument(doc *T, ref ComponentRef) (string, bool) {
if ref == nil || ref.RefString() == "" {
return "", false
}
// Case 1:
// Something like: ../another-folder/document.json#/myElement
if isRemoteReference(ref.RefString()) && isRootComponentReference(ref.RefString(), ref.CollectionName()) {
// Determine if it is *this* root doc.
if referencesRootDocument(doc, ref) {
_, name, _ := strings.Cut(ref.RefString(), path.Join("#/components/", ref.CollectionName()))
return path.Join("#/components/", ref.CollectionName(), name), true
}
}
// If there are no schemas defined in the root document return early.
if doc.Components == nil {
return "", false
}
collection, _, err := jsonpointer.GetForToken(doc.Components, ref.CollectionName())
if err != nil {
panic(err) // unreachable
}
var components map[string]ComponentRef
componentRefType := reflect.TypeFor[ComponentRef]()
if t := reflect.TypeOf(collection); t.Kind() == reflect.Map &&
t.Key().Kind() == reflect.String &&
t.Elem().AssignableTo(componentRefType) {
v := reflect.ValueOf(collection)
components = make(map[string]ComponentRef, v.Len())
for _, key := range v.MapKeys() {
strct := v.MapIndex(key)
// Type assertion safe, already checked via reflection above.
components[key.Interface().(string)] = strct.Interface().(ComponentRef)
}
} else {
return "", false
}
// Case 2:
// Something like: ../openapi.yaml#/components/schemas/myElement
for _, name := range componentNames(components) {
s := components[name]
// Must be a reference to a YAML file.
if !isWholeDocumentReference(s.RefString()) {
continue
}
// Is the schema a ref to the same resource.
if !refersToSameDocument(s, ref) {
continue
}
// Transform the remote ref to the equivalent schema in the root document.
return path.Join("#/components/", ref.CollectionName(), name), true
}
return "", false
}
// isElementReference takes a $ref value and checks if it references a specific element.
func isElementReference(ref string) bool {
return ref != "" && !isWholeDocumentReference(ref)
}
// isSchemaReference takes a $ref value and checks if it references a schema element.
func isRootComponentReference(ref string, compType string) bool {
return isElementReference(ref) && strings.Contains(ref, path.Join("#/components/", compType))
}
// isWholeDocumentReference takes a $ref value and checks if it is whole document reference.
func isWholeDocumentReference(ref string) bool {
return ref != "" && !strings.ContainsAny(ref, "#")
}
// isRemoteReference takes a $ref value and checks if it is remote reference.
func isRemoteReference(ref string) bool {
return ref != "" && !strings.HasPrefix(ref, "#") && !isURLReference(ref)
}
// isURLReference takes a $ref value and checks if it is URL reference.
func isURLReference(ref string) bool {
return strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") || strings.HasPrefix(ref, "//")
}

119
vendor/github.com/getkin/kin-openapi/openapi3/info.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Info is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#info-object
type Info struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Title string `json:"title" yaml:"title"` // Required
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` // OpenAPI >=3.1
Description string `json:"description,omitempty" yaml:"description,omitempty"`
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
License *License `json:"license,omitempty" yaml:"license,omitempty"`
Version string `json:"version" yaml:"version"` // Required
}
// MarshalJSON returns the JSON encoding of Info.
func (info Info) MarshalJSON() ([]byte, error) {
x, err := info.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Info.
func (info *Info) MarshalYAML() (any, error) {
if info == nil {
return nil, nil
}
m := make(map[string]any, 7+len(info.Extensions))
maps.Copy(m, info.Extensions)
m["title"] = info.Title
if x := info.Summary; x != "" {
m["summary"] = x
}
if x := info.Description; x != "" {
m["description"] = x
}
if x := info.TermsOfService; x != "" {
m["termsOfService"] = x
}
if x := info.Contact; x != nil {
m["contact"] = x
}
if x := info.License; x != nil {
m["license"] = x
}
m["version"] = info.Version
return m, nil
}
// UnmarshalJSON sets Info to a copy of data.
func (info *Info) UnmarshalJSON(data []byte) error {
type InfoBis Info
var x InfoBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "title")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "termsOfService")
delete(x.Extensions, "contact")
delete(x.Extensions, "license")
delete(x.Extensions, "version")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*info = Info(x)
return nil
}
// Validate returns an error if Info does not comply with the OpenAPI spec.
func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if info.Summary != "" && !getValidationOptions(ctx).isOpenAPI31OrLater {
if err := me.emit(newInfoSummaryFieldFor31Plus(info.Origin)); err != nil {
return err
}
}
if contact := info.Contact; contact != nil {
if err := me.emit(contact.Validate(ctx)); err != nil {
return err
}
}
if license := info.License; license != nil {
if err := me.emit(license.Validate(ctx)); err != nil {
return err
}
}
if info.Version == "" {
if err := me.emit(newInfoVersionRequired(info.Origin)); err != nil {
return err
}
}
if info.Title == "" {
if err := me.emit(newInfoTitleRequired(info.Origin)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, info.Extensions, info.Origin))
}

View File

@@ -0,0 +1,551 @@
package openapi3
import (
"context"
"path"
"strings"
)
// RefNameResolver maps a component to a name that is used as it's internalized name.
//
// The function should avoid name collisions (i.e. be a injective mapping).
// It must only contain characters valid for fixed field names: [IdentifierRegExp].
type RefNameResolver func(*T, ComponentRef) string
// DefaultRefResolver is a default implementation of refNameResolver for the
// InternalizeRefs function.
//
// The external reference is internalized to (hopefully) a unique name. If
// the external reference matches (by path) to another reference in the root
// document then the name of that component is used.
//
// The transformation involves:
// - Cutting the "#/components/<type>" part.
// - Cutting the file extensions (.yaml/.json) from documents.
// - Trimming the common directory with the root spec.
// - Replace invalid characters with underscores.
//
// This is an injective mapping over a "reasonable" amount of the possible openapi
// spec domain space but is not perfect. There might be edge cases.
func DefaultRefNameResolver(doc *T, ref ComponentRef) string {
if ref.RefString() == "" || ref.RefPath() == nil {
panic("unable to resolve reference to name")
}
name := ref.RefPath()
// If referring to a component in the root spec, no need to internalize just use
// the existing component.
// XXX(percivalalb): since this function call is iterating over components behind the
// scenes during an internalization call it actually starts interating over
// new & replaced internalized components. This might caused some edge cases,
// haven't found one yet but this might need to actually be used on a frozen copy
// of doc.
if nameInRoot, found := ReferencesComponentInRootDocument(doc, ref); found {
nameInRoot = strings.TrimPrefix(nameInRoot, "#")
rootCompURI := copyURI(doc.url)
rootCompURI.Fragment = nameInRoot
name = rootCompURI
}
filePath, componentPath := name.Path, name.Fragment
// Cut out the "#/components/<type>" to make the names shorter.
// XXX(percivalalb): This might cause collisions but is worth the brevity.
if b, a, ok := strings.Cut(componentPath, path.Join("components", ref.CollectionName(), "")); ok {
componentPath = path.Join(b, a)
}
if filePath != "" {
// If the path is the same as the root doc, just remove.
if doc.url != nil && filePath == doc.url.Path {
filePath = ""
}
// Remove the path extensions to make this JSON/YAML agnostic.
for ext := path.Ext(filePath); len(ext) > 0; ext = path.Ext(filePath) {
filePath = strings.TrimSuffix(filePath, ext)
}
// Trim the common prefix with the root doc path.
if doc.url != nil {
for commonDir := path.Dir(doc.url.Path); /*no common prefix*/ commonDir != "."; commonDir = path.Dir(commonDir) {
if p, found := cutDirectories(filePath, commonDir); found {
filePath = p
break
}
}
}
}
var internalizedName string
// Trim .'s & slashes from start e.g. otherwise ./doc.yaml would end up as __doc
if filePath != "" {
internalizedName = strings.TrimLeft(filePath, "./")
}
if componentPath != "" {
if internalizedName != "" {
internalizedName += "_"
}
internalizedName += strings.TrimLeft(componentPath, "./")
}
// Replace invalid characters in component fixed field names.
internalizedName = InvalidIdentifierCharRegExp.ReplaceAllString(internalizedName, "_")
return internalizedName
}
// cutDirectories removes the given directories from the start of the path if
// the path is a child.
func cutDirectories(p, dirs string) (string, bool) {
if dirs == "" || p == "" {
return p, false
}
p = strings.TrimRight(p, "/")
dirs = strings.TrimRight(dirs, "/")
var sb strings.Builder
sb.Grow(len(ParameterInHeader))
for segments := range strings.SplitSeq(p, "/") {
sb.WriteString(segments)
if sb.String() == p {
return strings.TrimPrefix(p, dirs), true
}
sb.WriteRune('/')
}
return p, false
}
func isExternalRef(ref string, parentIsExternal bool) bool {
return ref != "" && (!strings.HasPrefix(ref, "#/components/") || parentIsExternal)
}
func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if s == nil || !isExternalRef(s.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, s)
if doc.Components != nil {
if _, ok := doc.Components.Schemas[name]; ok {
s.Ref = "#/components/schemas/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Schemas == nil {
doc.Components.Schemas = make(Schemas)
}
doc.Components.Schemas[name] = s.Value.NewRef()
s.Ref = "#/components/schemas/" + name
return true
}
func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if p == nil || !isExternalRef(p.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, p)
if doc.Components != nil {
if _, ok := doc.Components.Parameters[name]; ok {
p.Ref = "#/components/parameters/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Parameters == nil {
doc.Components.Parameters = make(ParametersMap)
}
doc.Components.Parameters[name] = &ParameterRef{Value: p.Value}
p.Ref = "#/components/parameters/" + name
return true
}
func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if h == nil || !isExternalRef(h.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, h)
if doc.Components != nil {
if _, ok := doc.Components.Headers[name]; ok {
h.Ref = "#/components/headers/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Headers == nil {
doc.Components.Headers = make(Headers)
}
doc.Components.Headers[name] = &HeaderRef{Value: h.Value}
h.Ref = "#/components/headers/" + name
return true
}
func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, r)
if doc.Components != nil {
if _, ok := doc.Components.RequestBodies[name]; ok {
r.Ref = "#/components/requestBodies/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.RequestBodies == nil {
doc.Components.RequestBodies = make(RequestBodies)
}
doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value}
r.Ref = "#/components/requestBodies/" + name
return true
}
func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, r)
if doc.Components != nil {
if _, ok := doc.Components.Responses[name]; ok {
r.Ref = "#/components/responses/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Responses == nil {
doc.Components.Responses = make(ResponseBodies)
}
doc.Components.Responses[name] = &ResponseRef{Value: r.Value}
r.Ref = "#/components/responses/" + name
return true
}
func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) {
return
}
name := refNameResolver(doc, ss)
if doc.Components != nil {
if _, ok := doc.Components.SecuritySchemes[name]; ok {
ss.Ref = "#/components/securitySchemes/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.SecuritySchemes == nil {
doc.Components.SecuritySchemes = make(SecuritySchemes)
}
doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value}
ss.Ref = "#/components/securitySchemes/" + name
}
func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if e == nil || !isExternalRef(e.Ref, parentIsExternal) {
return
}
name := refNameResolver(doc, e)
if doc.Components != nil {
if _, ok := doc.Components.Examples[name]; ok {
e.Ref = "#/components/examples/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Examples == nil {
doc.Components.Examples = make(Examples)
}
doc.Components.Examples[name] = &ExampleRef{Value: e.Value}
e.Ref = "#/components/examples/" + name
}
func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if l == nil || !isExternalRef(l.Ref, parentIsExternal) {
return
}
name := refNameResolver(doc, l)
if doc.Components != nil {
if _, ok := doc.Components.Links[name]; ok {
l.Ref = "#/components/links/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Links == nil {
doc.Components.Links = make(Links)
}
doc.Components.Links[name] = &LinkRef{Value: l.Value}
l.Ref = "#/components/links/" + name
}
func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if c == nil || !isExternalRef(c.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, c)
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Callbacks == nil {
doc.Components.Callbacks = make(Callbacks)
}
c.Ref = "#/components/callbacks/" + name
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
return true
}
func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsExternal bool) {
if s == nil || doc.isVisitedSchema(s) {
return
}
for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} {
for _, s2 := range list {
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
if s2 != nil {
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
}
}
}
// Discriminator mapping values are special cases since they are not full
// ref objects but are string references to schema objects.
if s.Discriminator != nil {
for _, k := range componentNames(s.Discriminator.Mapping) {
mapRef := s.Discriminator.Mapping[k]
s2 := (*SchemaRef)(&mapRef)
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
s.Discriminator.Mapping[k] = MappingRef(*s2)
}
}
for _, name := range componentNames(s.Properties) {
s2 := s.Properties[name]
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
if s2 != nil {
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
}
}
for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties.Schema, s.Items} {
isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal)
if ref != nil {
doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal)
}
}
}
func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(hs) {
h := hs[name]
isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal)
if doc.isVisitedHeader(h.Value) {
continue
}
doc.derefParameter(h.Value.Parameter, refNameResolver, parentIsExternal || isExternal)
}
}
func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(es) {
e := es[name]
doc.addExampleToSpec(e, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(c) {
mediatype := c[name]
isExternal := doc.addSchemaToSpec(mediatype.Schema, refNameResolver, parentIsExternal)
if mediatype.Schema != nil {
doc.derefSchema(mediatype.Schema.Value, refNameResolver, isExternal || parentIsExternal)
}
doc.derefExamples(mediatype.Examples, refNameResolver, parentIsExternal)
for _, name := range componentNames(mediatype.Encoding) {
e := mediatype.Encoding[name]
doc.derefHeaders(e.Headers, refNameResolver, parentIsExternal)
}
}
}
func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(ls) {
l := ls[name]
doc.addLinkToSpec(l, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefResponse(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) {
isExternal := doc.addResponseToSpec(r, refNameResolver, parentIsExternal)
if v := r.Value; v != nil {
doc.derefHeaders(v.Headers, refNameResolver, isExternal || parentIsExternal)
doc.derefContent(v.Content, refNameResolver, isExternal || parentIsExternal)
doc.derefLinks(v.Links, refNameResolver, isExternal || parentIsExternal)
}
}
func (doc *T) derefResponses(rs *Responses, refNameResolver RefNameResolver, parentIsExternal bool) {
doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal)
}
func (doc *T) derefResponseBodies(es ResponseBodies, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(es) {
e := es[name]
doc.derefResponse(e, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver, parentIsExternal bool) {
isExternal := doc.addSchemaToSpec(p.Schema, refNameResolver, parentIsExternal)
doc.derefContent(p.Content, refNameResolver, parentIsExternal)
if p.Schema != nil {
doc.derefSchema(p.Schema.Value, refNameResolver, isExternal || parentIsExternal)
}
}
func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, parentIsExternal bool) {
doc.derefContent(r.Content, refNameResolver, parentIsExternal)
}
func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(paths) {
ops := paths[name]
pathIsExternal := isExternalRef(ops.Ref, parentIsExternal)
// inline full operations
ops.Ref = ""
for _, param := range ops.Parameters {
isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
if param.Value != nil {
doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
}
}
opsWithMethod := ops.Operations()
for _, name := range componentNames(opsWithMethod) {
op := opsWithMethod[name]
isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, pathIsExternal)
if op.RequestBody != nil && op.RequestBody.Value != nil {
doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, pathIsExternal || isExternal)
}
for _, name := range componentNames(op.Callbacks) {
cb := op.Callbacks[name]
isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal)
if cb.Value != nil {
cbValue := (*cb.Value).Map()
doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal)
}
}
doc.derefResponses(op.Responses, refNameResolver, pathIsExternal)
for _, param := range op.Parameters {
isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
if param.Value != nil {
doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
}
}
}
}
}
// InternalizeRefs removes all references to external files from the spec and moves them
// to the components section.
//
// refNameResolver takes in references to returns a name to store the reference under locally.
// It MUST return a unique name for each reference type.
// A default implementation is provided that will suffice for most use cases. See the function
// documentation for more details.
//
// Example:
//
// doc.InternalizeRefs(context.Background(), nil)
func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(*T, ComponentRef) string) {
doc.resetVisited()
if refNameResolver == nil {
refNameResolver = DefaultRefNameResolver
}
if components := doc.Components; components != nil {
for _, name := range componentNames(components.Schemas) {
schema := components.Schemas[name]
isExternal := doc.addSchemaToSpec(schema, refNameResolver, false)
if schema != nil {
schema.Ref = "" // always dereference the top level
doc.derefSchema(schema.Value, refNameResolver, isExternal)
}
}
for _, name := range componentNames(components.Parameters) {
p := components.Parameters[name]
isExternal := doc.addParameterToSpec(p, refNameResolver, false)
if p != nil && p.Value != nil {
p.Ref = "" // always dereference the top level
doc.derefParameter(*p.Value, refNameResolver, isExternal)
}
}
doc.derefHeaders(components.Headers, refNameResolver, false)
for _, name := range componentNames(components.RequestBodies) {
req := components.RequestBodies[name]
isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false)
if req != nil && req.Value != nil {
req.Ref = "" // always dereference the top level
doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
}
}
doc.derefResponseBodies(components.Responses, refNameResolver, false)
for _, name := range componentNames(components.SecuritySchemes) {
ss := components.SecuritySchemes[name]
doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
}
doc.derefExamples(components.Examples, refNameResolver, false)
doc.derefLinks(components.Links, refNameResolver, false)
for _, name := range componentNames(components.Callbacks) {
cb := components.Callbacks[name]
isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
if cb != nil && cb.Value != nil {
cb.Ref = "" // always dereference the top level
cbValue := (*cb.Value).Map()
doc.derefPaths(cbValue, refNameResolver, isExternal)
}
}
}
doc.derefPaths(doc.Paths.Map(), refNameResolver, false)
}

View File

@@ -0,0 +1,89 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// License is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#license-object
type License struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Name string `json:"name" yaml:"name"` // Required
URL string `json:"url,omitempty" yaml:"url,omitempty"`
// Identifier is an SPDX license expression for the API (OpenAPI 3.1)
// Either url or identifier can be specified, not both
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // OpenAPI >=3.1
}
// MarshalJSON returns the JSON encoding of License.
func (license License) MarshalJSON() ([]byte, error) {
x, err := license.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of License.
func (license License) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(license.Extensions))
maps.Copy(m, license.Extensions)
m["name"] = license.Name
if x := license.URL; x != "" {
m["url"] = x
}
if x := license.Identifier; x != "" {
m["identifier"] = x
}
return m, nil
}
// UnmarshalJSON sets License to a copy of data.
func (license *License) UnmarshalJSON(data []byte) error {
type LicenseBis License
var x LicenseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "url")
delete(x.Extensions, "identifier")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*license = License(x)
return nil
}
// Validate returns an error if License does not comply with the OpenAPI spec.
func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if license.Identifier != "" && !getValidationOptions(ctx).isOpenAPI31OrLater {
if err := me.emit(newLicenseIdentifierFieldFor31Plus(license.Origin)); err != nil {
return err
}
}
if license.Name == "" {
if err := me.emit(newLicenseNameRequired(license.Origin)); err != nil {
return err
}
}
if license.URL != "" && license.Identifier != "" {
if err := me.emit(newLicenseURLIdentifierExclusive(license.Origin)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, license.Extensions, license.Origin))
}

99
vendor/github.com/getkin/kin-openapi/openapi3/link.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Link is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
type Link struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
RequestBody any `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
}
// MarshalJSON returns the JSON encoding of Link.
func (link Link) MarshalJSON() ([]byte, error) {
x, err := link.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Link.
func (link Link) MarshalYAML() (any, error) {
m := make(map[string]any, 6+len(link.Extensions))
maps.Copy(m, link.Extensions)
if x := link.OperationRef; x != "" {
m["operationRef"] = x
}
if x := link.OperationID; x != "" {
m["operationId"] = x
}
if x := link.Description; x != "" {
m["description"] = x
}
if x := link.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := link.Server; x != nil {
m["server"] = x
}
if x := link.RequestBody; x != nil {
m["requestBody"] = x
}
return m, nil
}
// UnmarshalJSON sets Link to a copy of data.
func (link *Link) UnmarshalJSON(data []byte) error {
type LinkBis Link
var x LinkBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "operationRef")
delete(x.Extensions, "operationId")
delete(x.Extensions, "description")
delete(x.Extensions, "parameters")
delete(x.Extensions, "server")
delete(x.Extensions, "requestBody")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*link = Link(x)
return nil
}
// Validate returns an error if Link does not comply with the OpenAPI spec.
func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if link.OperationID == "" && link.OperationRef == "" {
return newLinkOperationIDOrRefRequired(link.Origin)
}
if link.OperationID != "" && link.OperationRef != "" {
return newLinkOperationIDRefExclusive(link.OperationID, link.OperationRef, link.Origin)
}
return validateExtensions(ctx, link.Extensions, link.Origin)
}
// UnmarshalJSON sets Links to a copy of data.
func (links *Links) UnmarshalJSON(data []byte) (err error) {
*links, err = unmarshalStringMapP[LinkRef](data)
return
}

1503
vendor/github.com/getkin/kin-openapi/openapi3/loader.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
package openapi3
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sync"
)
// ReadFromURIFunc defines a function which reads the contents of a resource
// located at a URI.
type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
var uriMu = &sync.RWMutex{}
// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a
// given URI.
var ErrURINotSupported = errors.New("unsupported URI")
// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the
// given reader functions, in the same order. If a reader function does not
// support the URI and returns ErrURINotSupported, the next function is checked
// until a match is found, or the URI is not supported by any.
func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc {
return func(loader *Loader, url *url.URL) ([]byte, error) {
for i := range readers {
buf, err := readers[i](loader, url)
if err == ErrURINotSupported {
continue
} else if err != nil {
return nil, err
}
return buf, nil
}
return nil, ErrURINotSupported
}
}
// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote
// HTTP URIs and local file URIs.
var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile))
// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to
// read the contents from a remote HTTP URI. This client may be customized to
// implement timeouts, RFC 7234 caching, etc.
func ReadFromHTTP(cl *http.Client) ReadFromURIFunc {
return func(loader *Loader, location *url.URL) ([]byte, error) {
if location.Scheme == "" || location.Host == "" {
return nil, ErrURINotSupported
}
req, err := http.NewRequest("GET", location.String(), nil)
if err != nil {
return nil, err
}
resp, err := cl.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode > 399 {
return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
}
func is_file(location *url.URL) bool {
return location.Path != "" &&
location.Host == "" &&
(location.Scheme == "" || location.Scheme == "file")
}
// ReadFromFile is a ReadFromURIFunc which reads local file URIs.
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) {
if !is_file(location) {
return nil, ErrURINotSupported
}
return os.ReadFile(path.Clean(filepath.FromSlash(location.Path)))
}
// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI
// locations in a simple map. This cache implementation is suitable for
// short-lived processes such as command-line tools which process OpenAPI
// documents.
func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc {
cache := map[string][]byte{}
return func(loader *Loader, location *url.URL) (buf []byte, err error) {
if location.Scheme == "" || location.Scheme == "file" {
if !filepath.IsAbs(location.Path) {
// Do not cache relative file paths; this can cause trouble if
// the current working directory changes when processing
// multiple top-level documents.
return reader(loader, location)
}
}
uri := location.String()
var ok bool
uriMu.RLock()
if buf, ok = cache[uri]; ok {
uriMu.RUnlock()
return
}
uriMu.RUnlock()
if buf, err = reader(loader, location); err != nil {
return
}
uriMu.Lock()
defer uriMu.Unlock()
cache[uri] = buf
return
}
}

View File

@@ -0,0 +1,384 @@
package openapi3
import (
"encoding/json"
"maps"
"strings"
"github.com/go-openapi/jsonpointer"
)
// NewResponsesWithCapacity builds a responses object of the given capacity.
func NewResponsesWithCapacity(cap int) *Responses {
if cap == 0 {
return &Responses{m: make(map[string]*ResponseRef)}
}
return &Responses{m: make(map[string]*ResponseRef, cap)}
}
// Keys returns the responses keys in a fixed order
func (responses *Responses) Keys() []string {
return componentNames(responses.Map())
}
// Value returns the responses for key or nil
func (responses *Responses) Value(key string) *ResponseRef {
if responses.Len() == 0 {
return nil
}
return responses.m[key]
}
// Set adds or replaces key 'key' of 'responses' with 'value'.
// Note: 'responses' MUST be non-nil
func (responses *Responses) Set(key string, value *ResponseRef) {
if responses.m == nil {
responses.m = make(map[string]*ResponseRef)
}
responses.m[key] = value
}
// Len returns the amount of keys in responses excluding responses.Extensions.
func (responses *Responses) Len() int {
if responses == nil || responses.m == nil {
return 0
}
return len(responses.m)
}
// Delete removes the entry associated with key 'key' from 'responses'.
func (responses *Responses) Delete(key string) {
if responses != nil && responses.m != nil {
delete(responses.m, key)
}
}
// Map returns responses as a 'map'.
// Note: iteration on Go maps is not ordered.
func (responses *Responses) Map() (m map[string]*ResponseRef) {
if responses == nil || len(responses.m) == 0 {
return make(map[string]*ResponseRef)
}
m = make(map[string]*ResponseRef, len(responses.m))
maps.Copy(m, responses.m)
return
}
var _ jsonpointer.JSONPointable = (*Responses)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (responses Responses) JSONLookup(token string) (any, error) {
if v := responses.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(responses.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
// MarshalYAML returns the YAML encoding of Responses.
func (responses *Responses) MarshalYAML() (any, error) {
if responses == nil {
return nil, nil
}
m := make(map[string]any, responses.Len()+len(responses.Extensions))
maps.Copy(m, responses.Extensions)
for _, k := range responses.Keys() {
m[k] = responses.m[k]
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Responses.
func (responses *Responses) MarshalJSON() ([]byte, error) {
responsesYaml, err := responses.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(responsesYaml)
}
// UnmarshalJSON sets Responses to a copy of data.
func (responses *Responses) UnmarshalJSON(data []byte) (err error) {
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return
}
x := Responses{
Extensions: make(map[string]any),
m: make(map[string]*ResponseRef, len(m)),
}
for _, k := range componentNames(m) {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv ResponseRef
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*responses = x
return
}
// NewCallbackWithCapacity builds a callback object of the given capacity.
func NewCallbackWithCapacity(cap int) *Callback {
if cap == 0 {
return &Callback{m: make(map[string]*PathItem)}
}
return &Callback{m: make(map[string]*PathItem, cap)}
}
// Keys returns the callback keys in a fixed order
func (callback *Callback) Keys() []string {
return componentNames(callback.Map())
}
// Value returns the callback for key or nil
func (callback *Callback) Value(key string) *PathItem {
if callback.Len() == 0 {
return nil
}
return callback.m[key]
}
// Set adds or replaces key 'key' of 'callback' with 'value'.
// Note: 'callback' MUST be non-nil
func (callback *Callback) Set(key string, value *PathItem) {
if callback.m == nil {
callback.m = make(map[string]*PathItem)
}
callback.m[key] = value
}
// Len returns the amount of keys in callback excluding callback.Extensions.
func (callback *Callback) Len() int {
if callback == nil || callback.m == nil {
return 0
}
return len(callback.m)
}
// Delete removes the entry associated with key 'key' from 'callback'.
func (callback *Callback) Delete(key string) {
if callback != nil && callback.m != nil {
delete(callback.m, key)
}
}
// Map returns callback as a 'map'.
// Note: iteration on Go maps is not ordered.
func (callback *Callback) Map() (m map[string]*PathItem) {
if callback == nil || len(callback.m) == 0 {
return make(map[string]*PathItem)
}
m = make(map[string]*PathItem, len(callback.m))
maps.Copy(m, callback.m)
return
}
var _ jsonpointer.JSONPointable = (*Callback)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (callback Callback) JSONLookup(token string) (any, error) {
if v := callback.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(callback.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v, nil
}
}
// MarshalYAML returns the YAML encoding of Callback.
func (callback *Callback) MarshalYAML() (any, error) {
if callback == nil {
return nil, nil
}
m := make(map[string]any, callback.Len()+len(callback.Extensions))
maps.Copy(m, callback.Extensions)
for _, k := range callback.Keys() {
m[k] = callback.m[k]
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Callback.
func (callback *Callback) MarshalJSON() ([]byte, error) {
callbackYaml, err := callback.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(callbackYaml)
}
// UnmarshalJSON sets Callback to a copy of data.
func (callback *Callback) UnmarshalJSON(data []byte) (err error) {
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return
}
x := Callback{
Extensions: make(map[string]any),
m: make(map[string]*PathItem, len(m)),
}
for _, k := range componentNames(m) {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv PathItem
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*callback = x
return
}
// NewPathsWithCapacity builds a paths object of the given capacity.
func NewPathsWithCapacity(cap int) *Paths {
if cap == 0 {
return &Paths{m: make(map[string]*PathItem)}
}
return &Paths{m: make(map[string]*PathItem, cap)}
}
// Keys returns the paths keys in a fixed order
func (paths *Paths) Keys() []string {
return componentNames(paths.Map())
}
// Value returns the paths for key or nil
func (paths *Paths) Value(key string) *PathItem {
if paths.Len() == 0 {
return nil
}
return paths.m[key]
}
// Set adds or replaces key 'key' of 'paths' with 'value'.
// Note: 'paths' MUST be non-nil
func (paths *Paths) Set(key string, value *PathItem) {
if paths.m == nil {
paths.m = make(map[string]*PathItem)
}
paths.m[key] = value
}
// Len returns the amount of keys in paths excluding paths.Extensions.
func (paths *Paths) Len() int {
if paths == nil || paths.m == nil {
return 0
}
return len(paths.m)
}
// Delete removes the entry associated with key 'key' from 'paths'.
func (paths *Paths) Delete(key string) {
if paths != nil && paths.m != nil {
delete(paths.m, key)
}
}
// Map returns paths as a 'map'.
// Note: iteration on Go maps is not ordered.
func (paths *Paths) Map() (m map[string]*PathItem) {
if paths == nil || len(paths.m) == 0 {
return make(map[string]*PathItem)
}
m = make(map[string]*PathItem, len(paths.m))
maps.Copy(m, paths.m)
return
}
var _ jsonpointer.JSONPointable = (*Paths)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (paths Paths) JSONLookup(token string) (any, error) {
if v := paths.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(paths.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v, nil
}
}
// MarshalYAML returns the YAML encoding of Paths.
func (paths *Paths) MarshalYAML() (any, error) {
if paths == nil {
return nil, nil
}
m := make(map[string]any, paths.Len()+len(paths.Extensions))
maps.Copy(m, paths.Extensions)
for _, k := range paths.Keys() {
m[k] = paths.m[k]
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Paths.
func (paths *Paths) MarshalJSON() ([]byte, error) {
pathsYaml, err := paths.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(pathsYaml)
}
// UnmarshalJSON sets Paths to a copy of data.
func (paths *Paths) UnmarshalJSON(data []byte) (err error) {
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return
}
x := Paths{
Extensions: make(map[string]any),
m: make(map[string]*PathItem, len(m)),
}
for _, k := range componentNames(m) {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv PathItem
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*paths = x
return
}

45
vendor/github.com/getkin/kin-openapi/openapi3/marsh.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package openapi3
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/oasdiff/yaml"
)
func unmarshalError(jsonUnmarshalErr error) error {
if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" {
before = strings.ReplaceAll(before, " Go struct ", " ")
return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", ""))
}
return jsonUnmarshalErr
}
func unmarshal(data []byte, v any, includeOrigin bool, location *url.URL) error {
var jsonErr, yamlErr error
// See https://github.com/getkin/kin-openapi/issues/680
if jsonErr = json.Unmarshal(data, v); jsonErr == nil {
return nil
}
// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
var file string
if location != nil {
file = location.String()
}
if tree, err := yaml.Unmarshal(data, v, yaml.DecodeOpts{
Origin: yaml.OriginOpt{Enabled: includeOrigin, File: file},
DisableTimestamps: true,
}); err == nil {
applyOrigins(v, tree)
return nil
} else {
yamlErr = err
}
// If both unmarshaling attempts fail, return a new error that includes both errors
return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr)
}

View File

@@ -0,0 +1,174 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
"github.com/go-openapi/jsonpointer"
)
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
type MediaType struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding Encodings `json:"encoding,omitempty" yaml:"encoding,omitempty"`
}
var _ jsonpointer.JSONPointable = (*MediaType)(nil)
func NewMediaType() *MediaType {
return &MediaType{}
}
func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType {
if schema == nil {
mediaType.Schema = nil
} else {
mediaType.Schema = &SchemaRef{Value: schema}
}
return mediaType
}
func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType {
mediaType.Schema = schema
return mediaType
}
func (mediaType *MediaType) WithExample(name string, value any) *MediaType {
example := mediaType.Examples
if example == nil {
example = make(map[string]*ExampleRef)
mediaType.Examples = example
}
example[name] = &ExampleRef{
Value: NewExample(value),
}
return mediaType
}
func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType {
encoding := mediaType.Encoding
if encoding == nil {
encoding = make(Encodings)
mediaType.Encoding = encoding
}
encoding[name] = enc
return mediaType
}
// MarshalJSON returns the JSON encoding of MediaType.
func (mediaType MediaType) MarshalJSON() ([]byte, error) {
x, err := mediaType.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of MediaType.
func (mediaType MediaType) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(mediaType.Extensions))
maps.Copy(m, mediaType.Extensions)
if x := mediaType.Schema; x != nil {
m["schema"] = x
}
if x := mediaType.Example; x != nil {
m["example"] = x
}
if x := mediaType.Examples; len(x) != 0 {
m["examples"] = x
}
if x := mediaType.Encoding; len(x) != 0 {
m["encoding"] = x
}
return m, nil
}
// UnmarshalJSON sets MediaType to a copy of data.
func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
type MediaTypeBis MediaType
var x MediaTypeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "schema")
delete(x.Extensions, "example")
delete(x.Extensions, "examples")
delete(x.Extensions, "encoding")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
delete(x.Encoding, originKey)
*mediaType = MediaType(x)
return nil
}
// Validate returns an error if MediaType does not comply with the OpenAPI spec.
func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if mediaType == nil {
return nil
}
if schema := mediaType.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return err
}
if mediaType.Example != nil && mediaType.Examples != nil {
return newMediaTypeExampleExamplesExclusive(mediaType.Origin)
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
if example := mediaType.Example; example != nil {
if err := validateExampleValue(ctx, example, schema.Value); err != nil {
return newSchemaValueError("example", err, mediaType.Origin)
}
}
if examples := mediaType.Examples; examples != nil {
for _, k := range componentNames(examples) {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return &MediaTypeExampleValidationError{ExampleName: k, Cause: err}
}
if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
return newSchemaValueError("example",
&MediaTypeExampleValidationError{ExampleName: k, Cause: err},
exampleValueOrigin(v.Value, mediaType.Origin))
}
}
}
}
}
return validateExtensions(ctx, mediaType.Extensions, mediaType.Origin)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (mediaType MediaType) JSONLookup(token string) (any, error) {
switch token {
case "schema":
if mediaType.Schema != nil {
if mediaType.Schema.Ref != "" {
return &Ref{Ref: mediaType.Schema.Ref}, nil
}
return mediaType.Schema.Value, nil
}
case "example":
return mediaType.Example, nil
case "examples":
return mediaType.Examples, nil
case "encoding":
return mediaType.Encoding, nil
}
v, _, err := jsonpointer.GetForToken(mediaType.Extensions, token)
return v, err
}

View File

@@ -0,0 +1,391 @@
package openapi3
import (
"context"
"encoding/json"
"fmt"
"maps"
"net/url"
"slices"
"github.com/go-openapi/jsonpointer"
)
// T is the root of an OpenAPI v3 document
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object
// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object
type T struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
OpenAPI string `json:"openapi" yaml:"openapi"` // Required
Components *Components `json:"components,omitempty" yaml:"components,omitempty"`
Info *Info `json:"info" yaml:"info"` // Required
Paths *Paths `json:"paths" yaml:"paths"` // Required in 3.0, optional in 3.1
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Webhooks map[string]*PathItem `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` // OpenAPI >=3.1
JSONSchemaDialect string `json:"jsonSchemaDialect,omitempty" yaml:"jsonSchemaDialect,omitempty"` // OpenAPI >=3.1
visited visitedComponent
url *url.URL
// Document-scoped format validators
// These validators are automatically used by all schemas in this document
stringFormats map[string]StringFormatValidator
numberFormats map[string]NumberFormatValidator
integerFormats map[string]IntegerFormatValidator
}
// IsOpenAPI30 returns whether doc is an OpenAPI document version 3.0.x.
// Returns true for 3, 3.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, ...
// And false for 3.1.0, 3.2, ... and for invalid strings.
func (doc *T) IsOpenAPI30() bool {
return doc.OpenAPIMajorMinor() == "3.0"
}
// IsOpenAPI31OrLater returns whether doc is an OpenAPI document version >=3.1.
// Returns true for 3.1, 3.1.0, 3.1.1, 3.1.2, 3.2.0, ...
// And false for cases where IsOpenAPI30 returns true and for invalid strings.
func (doc *T) IsOpenAPI31OrLater() bool {
return slices.Contains([]string{"3.1", "3.2"}, doc.OpenAPIMajorMinor())
}
func errFieldFor31Plus(field string, origin *Origin) error {
return newFieldFor31Plus(field, origin)
}
func errValueOfFieldFor31Plus(value any, field string) error {
return fmt.Errorf("value %q of field %s is for OpenAPI >=3.1", value, field)
}
// OpenAPIMajorMinor returns 3.y of the OpenAPI "3.y" or "3.y.z" version of the document.
// Returns the empty string for invalid OpenAPI version strings.
func (doc *T) OpenAPIMajorMinor() string {
if doc == nil {
return ""
}
switch doc.OpenAPI {
case "3", "3.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4":
return "3.0"
case "3.1", "3.1.0", "3.1.1", "3.1.2":
return "3.1"
case "3.2", "3.2.0":
return "3.2"
default:
return ""
}
}
var _ jsonpointer.JSONPointable = (*T)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (doc *T) JSONLookup(token string) (any, error) {
switch token {
case "openapi":
return doc.OpenAPI, nil
case "components":
return doc.Components, nil
case "info":
return doc.Info, nil
case "paths":
return doc.Paths, nil
case "security":
return doc.Security, nil
case "servers":
return doc.Servers, nil
case "tags":
return doc.Tags, nil
case "externalDocs":
return doc.ExternalDocs, nil
case "webhooks":
return doc.Webhooks, nil
case "jsonSchemaDialect":
return doc.JSONSchemaDialect, nil
}
v, _, err := jsonpointer.GetForToken(doc.Extensions, token)
return v, err
}
// MarshalJSON returns the JSON encoding of T.
func (doc *T) MarshalJSON() ([]byte, error) {
x, err := doc.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of T.
func (doc *T) MarshalYAML() (any, error) {
if doc == nil {
return nil, nil
}
m := make(map[string]any, 10+len(doc.Extensions))
maps.Copy(m, doc.Extensions)
m["openapi"] = doc.OpenAPI
if x := doc.Components; x != nil {
m["components"] = x
}
m["info"] = doc.Info
m["paths"] = doc.Paths
if x := doc.Security; len(x) != 0 {
m["security"] = x
}
if x := doc.Servers; len(x) != 0 {
m["servers"] = x
}
if x := doc.Tags; len(x) != 0 {
m["tags"] = x
}
if x := doc.ExternalDocs; x != nil {
m["externalDocs"] = x
}
if x := doc.Webhooks; len(x) != 0 {
m["webhooks"] = x
}
if x := doc.JSONSchemaDialect; x != "" {
m["jsonSchemaDialect"] = x
}
return m, nil
}
// UnmarshalJSON sets T to a copy of data.
func (doc *T) UnmarshalJSON(data []byte) error {
type TBis T
var x TBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "openapi")
delete(x.Extensions, "components")
delete(x.Extensions, "info")
delete(x.Extensions, "paths")
delete(x.Extensions, "security")
delete(x.Extensions, "servers")
delete(x.Extensions, "tags")
delete(x.Extensions, "externalDocs")
delete(x.Extensions, "webhooks")
delete(x.Extensions, "jsonSchemaDialect")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
delete(x.Webhooks, originKey)
*doc = T(x)
return nil
}
func (doc *T) AddOperation(path string, method string, operation *Operation) {
if doc.Paths == nil {
doc.Paths = NewPaths()
}
pathItem := doc.Paths.Value(path)
if pathItem == nil {
pathItem = &PathItem{}
doc.Paths.Set(path, pathItem)
}
pathItem.SetOperation(method, operation)
}
func (doc *T) AddServer(server *Server) {
doc.Servers = append(doc.Servers, server)
}
func (doc *T) AddServers(servers ...*Server) {
doc.Servers = append(doc.Servers, servers...)
}
// SetStringFormatValidators sets document-scoped string format validators.
// These validators are automatically used by all schemas in this document.
func (doc *T) SetStringFormatValidators(validators map[string]StringFormatValidator) {
doc.stringFormats = validators
}
// SetStringFormatValidator sets a single document-scoped string format validator.
func (doc *T) SetStringFormatValidator(name string, validator StringFormatValidator) {
if doc.stringFormats == nil {
doc.stringFormats = make(map[string]StringFormatValidator)
}
doc.stringFormats[name] = validator
}
// SetNumberFormatValidators sets document-scoped number format validators.
// These validators are automatically used by all schemas in this document.
func (doc *T) SetNumberFormatValidators(validators map[string]NumberFormatValidator) {
doc.numberFormats = validators
}
// SetNumberFormatValidator sets a single document-scoped number format validator.
func (doc *T) SetNumberFormatValidator(name string, validator NumberFormatValidator) {
if doc.numberFormats == nil {
doc.numberFormats = make(map[string]NumberFormatValidator)
}
doc.numberFormats[name] = validator
}
// SetIntegerFormatValidators sets document-scoped integer format validators.
// These validators are automatically used by all schemas in this document.
func (doc *T) SetIntegerFormatValidators(validators map[string]IntegerFormatValidator) {
doc.integerFormats = validators
}
// SetIntegerFormatValidator sets a single document-scoped integer format validator.
func (doc *T) SetIntegerFormatValidator(name string, validator IntegerFormatValidator) {
if doc.integerFormats == nil {
doc.integerFormats = make(map[string]IntegerFormatValidator)
}
doc.integerFormats[name] = validator
}
// GetSchemaValidationOptions returns SchemaValidationOptions that include
// this document's format validators. Use this when validating schemas from this document.
func (doc *T) GetSchemaValidationOptions() []SchemaValidationOption {
var opts []SchemaValidationOption
if doc.stringFormats != nil {
opts = append(opts, WithStringFormatValidators(doc.stringFormats))
}
if doc.numberFormats != nil {
opts = append(opts, WithNumberFormatValidators(doc.numberFormats))
}
if doc.integerFormats != nil {
opts = append(opts, WithIntegerFormatValidators(doc.integerFormats))
}
return opts
}
// Validate returns an error if T does not comply with the OpenAPI spec.
// Validations Options can be provided to modify the validation behavior.
//
// By default, doc.OpenAPI's field dictates whether "JSON Schema Draft 2020-12" validation
// is enabled.
func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error {
if doc.IsOpenAPI31OrLater() {
opts = append(opts, IsOpenAPI31OrLater())
}
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if doc.OpenAPI == "" {
if err := me.emit(newOpenAPIVersionRequired(doc.Origin)); err != nil {
return err
}
}
if doc.Webhooks != nil && !doc.IsOpenAPI31OrLater() {
if err := me.emit(newWebhooksFieldFor31Plus(doc.Origin)); err != nil {
return err
}
}
if doc.JSONSchemaDialect != "" && !doc.IsOpenAPI31OrLater() {
if err := me.emit(newJSONSchemaDialectFieldFor31Plus(doc.Origin)); err != nil {
return err
}
}
wrapSection := func(section string) func(error) error {
return func(e error) error { return &SectionValidationError{Section: section, Cause: e} }
}
var wrap func(error) error
wrap = wrapSection("components")
if v := doc.Components; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
wrap = wrapSection("info")
if v := doc.Info; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
} else if err := me.emit(wrap(newInfoRequired(doc.Origin))); err != nil {
return err
}
wrap = wrapSection("paths")
if v := doc.Paths; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
} else if doc.IsOpenAPI30() {
if err := me.emit(wrap(newPathsRequired(doc.Origin))); err != nil {
return err
}
}
wrap = wrapSection("security")
if v := doc.Security; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
wrap = wrapSection("servers")
if v := doc.Servers; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
wrap = wrapSection("tags")
if v := doc.Tags; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
wrap = wrapSection("external docs")
if v := doc.ExternalDocs; v != nil {
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
wrap = wrapSection("webhooks")
for _, name := range componentNames(doc.Webhooks) {
pathItem := doc.Webhooks[name]
if pathItem == nil {
if err := me.emit(wrap(newWebhookNil(name))); err != nil {
return err
}
// Nothing to descend into for a nil webhook; the nil itself
// is the only finding under this name until the entry is
// populated, so continue to the next webhook.
continue
}
wrapWebhook := func(e error) error { return wrap(&WebhookValidationError{Name: name, Cause: e}) }
if err := me.emitWrapped(wrapWebhook, pathItem.Validate(ctx)); err != nil {
return err
}
}
wrap = wrapSection("jsonSchemaDialect")
if doc.JSONSchemaDialect != "" {
u, err := url.Parse(doc.JSONSchemaDialect)
if err != nil {
if err = me.emit(wrap(err)); err != nil {
return err
}
} else if u.Scheme == "" {
if err := me.emit(wrap(newJSONSchemaDialectAbsoluteURIRequired(doc.Origin))); err != nil {
return err
}
}
}
return me.finalize(validateExtensions(ctx, doc.Extensions, doc.Origin))
}
// ValidateSchemaJSON validates data against a schema using this document's format validators.
// This is a convenience method that automatically applies the document's format validators.
func (doc *T) ValidateSchemaJSON(schema *Schema, value any, opts ...SchemaValidationOption) error {
// Combine document's validators with any additional options
allOpts := append(doc.GetSchemaValidationOptions(), opts...)
return schema.VisitJSON(value, allOpts...)
}

View File

@@ -0,0 +1,222 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
"strconv"
"github.com/go-openapi/jsonpointer"
)
// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
type Operation struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
// Optional tags for documentation.
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
// Optional short summary.
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
// Optional description. Should use CommonMark syntax.
Description string `json:"description,omitempty" yaml:"description,omitempty"`
// Optional operation ID.
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
// Optional parameters.
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
// Optional body parameter.
RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
// Responses.
Responses *Responses `json:"responses" yaml:"responses"` // Required
// Optional callbacks
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
// Optional security requirements that overrides top-level security.
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
// Optional servers that overrides top-level servers.
Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
var _ jsonpointer.JSONPointable = (*Operation)(nil)
func NewOperation() *Operation {
return &Operation{}
}
// MarshalJSON returns the JSON encoding of Operation.
func (operation Operation) MarshalJSON() ([]byte, error) {
x, err := operation.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Operation.
func (operation Operation) MarshalYAML() (any, error) {
m := make(map[string]any, 12+len(operation.Extensions))
maps.Copy(m, operation.Extensions)
if x := operation.Tags; len(x) != 0 {
m["tags"] = x
}
if x := operation.Summary; x != "" {
m["summary"] = x
}
if x := operation.Description; x != "" {
m["description"] = x
}
if x := operation.OperationID; x != "" {
m["operationId"] = x
}
if x := operation.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := operation.RequestBody; x != nil {
m["requestBody"] = x
}
m["responses"] = operation.Responses
if x := operation.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
if x := operation.Deprecated; x {
m["deprecated"] = x
}
if x := operation.Security; x != nil {
m["security"] = x
}
if x := operation.Servers; x != nil {
m["servers"] = x
}
if x := operation.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return m, nil
}
// UnmarshalJSON sets Operation to a copy of data.
func (operation *Operation) UnmarshalJSON(data []byte) error {
type OperationBis Operation
var x OperationBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "tags")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "operationId")
delete(x.Extensions, "parameters")
delete(x.Extensions, "requestBody")
delete(x.Extensions, "responses")
delete(x.Extensions, "callbacks")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "security")
delete(x.Extensions, "servers")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*operation = Operation(x)
return nil
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (operation Operation) JSONLookup(token string) (any, error) {
switch token {
case "requestBody":
if operation.RequestBody != nil {
if operation.RequestBody.Ref != "" {
return &Ref{Ref: operation.RequestBody.Ref}, nil
}
return operation.RequestBody.Value, nil
}
case "tags":
return operation.Tags, nil
case "summary":
return operation.Summary, nil
case "description":
return operation.Description, nil
case "operationID":
return operation.OperationID, nil
case "parameters":
return operation.Parameters, nil
case "responses":
return operation.Responses, nil
case "callbacks":
return operation.Callbacks, nil
case "deprecated":
return operation.Deprecated, nil
case "security":
return operation.Security, nil
case "servers":
return operation.Servers, nil
case "externalDocs":
return operation.ExternalDocs, nil
}
v, _, err := jsonpointer.GetForToken(operation.Extensions, token)
return v, err
}
func (operation *Operation) AddParameter(p *Parameter) {
operation.Parameters = append(operation.Parameters, &ParameterRef{Value: p})
}
func (operation *Operation) AddResponse(status int, response *Response) {
code := "default"
if 0 < status && status < 1000 {
code = strconv.FormatInt(int64(status), 10)
}
if operation.Responses == nil {
operation.Responses = NewResponses()
}
operation.Responses.Set(code, &ResponseRef{Value: response})
}
// Validate returns an error if Operation does not comply with the OpenAPI spec.
func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if v := operation.Parameters; v != nil {
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
if v := operation.RequestBody; v != nil {
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
if v := operation.Responses; v != nil {
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
} else if err := me.emit(newOperationResponsesRequired(operation.Origin)); err != nil {
return err
}
if v := operation.ExternalDocs; v != nil {
wrap := func(e error) error { return &SectionValidationError{Section: "external docs", Cause: e} }
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, operation.Extensions, operation.Origin))
}

255
vendor/github.com/getkin/kin-openapi/openapi3/origin.go generated vendored Normal file
View File

@@ -0,0 +1,255 @@
package openapi3
import (
"reflect"
"strings"
"github.com/oasdiff/yaml"
)
const originKey = "__origin__"
var originPtrType = reflect.TypeFor[*Origin]()
// Origin contains the origin of a collection.
// Key is the location of the collection itself.
// Fields is a map of the location of each scalar field in the collection.
// Sequences is a map of the location of each item in sequence-valued fields.
type Origin struct {
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
Sequences map[string][]Location `json:"sequences,omitempty" yaml:"sequences,omitempty"`
}
// Location is a struct that contains the location of a field.
type Location struct {
File string `json:"file,omitempty" yaml:"file,omitempty"`
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
// originFromSeq parses the compact []any sequence produced by yaml3's addOrigin.
//
// Format: [file, key_name, key_line, key_col, nf, f1_name, f1_delta, f1_col, ..., ns, s1_name, s1_count, s1_l0_delta, s1_c0, ...]
func originFromSeq(s []any) *Origin {
// Need at least: file, key_name, key_line, key_col, nf, ns
if len(s) < 6 {
return nil
}
file, _ := s[0].(string)
keyName, _ := s[1].(string)
keyLine := toInt(s[2])
keyCol := toInt(s[3])
o := &Origin{
Key: &Location{
File: file,
Line: keyLine,
Column: keyCol,
Name: keyName,
},
}
idx := 4
nf := toInt(s[idx])
idx++
if nf > 0 && idx+nf*3 <= len(s) {
o.Fields = make(map[string]Location, nf)
for range nf {
fname, _ := s[idx].(string)
delta := toInt(s[idx+1])
col := toInt(s[idx+2])
o.Fields[fname] = Location{
File: file,
Line: keyLine + delta,
Column: col,
Name: fname,
}
idx += 3
}
}
if idx >= len(s) {
return o
}
ns := toInt(s[idx])
idx++
if ns > 0 {
o.Sequences = make(map[string][]Location, ns)
for range ns {
if idx >= len(s) {
break
}
sname, _ := s[idx].(string)
idx++
if idx >= len(s) {
break
}
count := toInt(s[idx])
idx++
locs := make([]Location, count)
for j := 0; j < count && idx+2 < len(s); j++ {
name, _ := s[idx].(string)
delta := toInt(s[idx+1])
col := toInt(s[idx+2])
locs[j] = Location{File: file, Line: keyLine + delta, Column: col, Name: name}
idx += 3
}
o.Sequences[sname] = locs
}
}
return o
}
// toInt converts numeric types to int. Handles int/uint64 from YAML decoding.
func toInt(v any) int {
switch n := v.(type) {
case int:
return n
case uint64:
return int(n)
}
return 0
}
// applyOrigins walks a Go struct tree and a parallel OriginTree, setting
// Origin fields on each struct from the extracted origin data.
func applyOrigins(v any, tree *yaml.OriginTree) {
if tree == nil {
return
}
applyOriginsToValue(reflect.ValueOf(v), tree)
}
func applyOriginsToValue(val reflect.Value, tree *yaml.OriginTree) {
// Keep track of the last pointer so we can pass it to struct handlers
// (needed for calling methods like Map() on maplike types).
var ptr reflect.Value
for val.Kind() == reflect.Pointer || val.Kind() == reflect.Interface {
if val.IsNil() {
return
}
if val.Kind() == reflect.Pointer {
ptr = val
}
val = val.Elem()
}
switch val.Kind() {
case reflect.Struct:
applyOriginsToStruct(val, ptr, tree)
case reflect.Map:
applyOriginsToMap(val, tree)
case reflect.Slice:
applyOriginsToSlice(val, tree)
}
}
func applyOriginsToStruct(val reflect.Value, ptr reflect.Value, tree *yaml.OriginTree) {
typ := val.Type()
// Set Origin field for structs whose Origin field has a "-" json tag.
if tree.Origin != nil {
if sf, ok := typ.FieldByName("Origin"); ok && sf.Type == originPtrType {
tag := sf.Tag.Get("json")
if tag == "-" {
if s, ok := tree.Origin.([]any); ok {
val.FieldByName("Origin").Set(reflect.ValueOf(originFromSeq(s)))
}
}
}
}
// Recurse into exported struct fields using json tags
for i := range typ.NumField() {
sf := typ.Field(i)
if !sf.IsExported() {
continue
}
tag := jsonTagName(sf)
if tag == "" || tag == "-" {
continue
}
childTree := tree.Fields[tag]
if childTree != nil {
applyOriginsToValue(val.Field(i), childTree)
}
}
// Handle wrapper types whose inner struct has no json tag:
// - *Ref types (e.g. SchemaRef, ResponseRef) have a "Value" field
// - BoolSchema (AdditionalProperties, UnevaluatedProperties, UnevaluatedItems) has a "Schema" field
// The origin tree data applies to the inner struct, not a sub-key.
for _, fieldName := range []string{"Value", "Schema"} {
vf := val.FieldByName(fieldName)
if !vf.IsValid() || vf.Kind() != reflect.Pointer || vf.IsNil() {
continue
}
sf, _ := typ.FieldByName(fieldName)
if sf.Tag.Get("json") == "" {
applyOriginsToValue(vf, tree)
}
}
// Handle "maplike" types (Paths, Responses, Callback) whose items are
// stored in an unexported map accessible via a Map() method.
// Use the original pointer (if available) since dereferenced values
// are not addressable.
receiver := val
if ptr.IsValid() {
receiver = ptr
} else if val.CanAddr() {
receiver = val.Addr()
}
if receiver.Kind() == reflect.Pointer {
if mapMethod := receiver.MethodByName("Map"); mapMethod.IsValid() {
results := mapMethod.Call(nil)
if len(results) == 1 {
applyOriginsToMap(results[0], tree)
}
}
}
}
func applyOriginsToMap(val reflect.Value, tree *yaml.OriginTree) {
if tree.Fields == nil {
return
}
for _, key := range val.MapKeys() {
childTree := tree.Fields[key.String()]
if childTree == nil {
continue
}
elem := val.MapIndex(key)
// Map values are not addressable. For pointer-typed values we can
// recurse directly. For value types we must copy, apply, and set back.
if elem.Kind() == reflect.Pointer || elem.Kind() == reflect.Interface {
applyOriginsToValue(elem, childTree)
} else if elem.Kind() == reflect.Struct {
// Copy to a settable value
cp := reflect.New(elem.Type()).Elem()
cp.Set(elem)
applyOriginsToStruct(cp, reflect.Value{}, childTree)
val.SetMapIndex(key, cp)
}
}
}
func applyOriginsToSlice(val reflect.Value, tree *yaml.OriginTree) {
for i := 0; i < val.Len() && i < len(tree.Items); i++ {
if tree.Items[i] != nil {
applyOriginsToValue(val.Index(i), tree.Items[i])
}
}
}
// jsonTagName returns the JSON field name from a struct field's json tag.
func jsonTagName(f reflect.StructField) string {
tag := f.Tag.Get("json")
if tag == "" {
return ""
}
name, _, _ := strings.Cut(tag, ",")
return name
}

View File

@@ -0,0 +1,417 @@
package openapi3
import (
"context"
"encoding/json"
"fmt"
"maps"
"strconv"
"github.com/go-openapi/jsonpointer"
)
// Parameters is specified by OpenAPI/Swagger 3.0 standard.
type Parameters []*ParameterRef
var _ jsonpointer.JSONPointable = (*Parameters)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (p Parameters) JSONLookup(token string) (any, error) {
index, err := strconv.Atoi(token)
if err != nil {
return nil, err
}
if index < 0 || index >= len(p) {
return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p))
}
ref := p[index]
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
func NewParameters() Parameters {
return make(Parameters, 0, 4)
}
func (parameters Parameters) GetByInAndName(in string, name string) *Parameter {
for _, item := range parameters {
if v := item.Value; v != nil {
if v.Name == name && v.In == in {
return v
}
}
}
return nil
}
// Validate returns an error if Parameters does not comply with the OpenAPI spec.
func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
dupes := make(map[string]struct{})
for _, parameterRef := range parameters {
if v := parameterRef.Value; v != nil {
key := v.In + ":" + v.Name
if _, ok := dupes[key]; ok {
return newDuplicateParameter(v.In, v.Name, v.Origin)
}
dupes[key] = struct{}{}
}
if err := parameterRef.Validate(ctx); err != nil {
return err
}
}
return nil
}
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
type Parameter struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
}
var _ jsonpointer.JSONPointable = (*Parameter)(nil)
const (
ParameterInPath = "path"
ParameterInQuery = "query"
ParameterInHeader = "header"
ParameterInCookie = "cookie"
)
func NewPathParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInPath,
Required: true,
}
}
func NewQueryParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInQuery,
}
}
func NewHeaderParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInHeader,
}
}
func NewCookieParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInCookie,
}
}
func (parameter *Parameter) WithDescription(value string) *Parameter {
parameter.Description = value
return parameter
}
func (parameter *Parameter) WithRequired(value bool) *Parameter {
parameter.Required = value
return parameter
}
func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
if value == nil {
parameter.Schema = nil
} else {
parameter.Schema = &SchemaRef{
Value: value,
}
}
return parameter
}
// MarshalJSON returns the JSON encoding of Parameter.
func (parameter Parameter) MarshalJSON() ([]byte, error) {
x, err := parameter.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Parameter.
func (parameter Parameter) MarshalYAML() (any, error) {
m := make(map[string]any, 13+len(parameter.Extensions))
maps.Copy(m, parameter.Extensions)
if x := parameter.Name; x != "" {
m["name"] = x
}
if x := parameter.In; x != "" {
m["in"] = x
}
if x := parameter.Description; x != "" {
m["description"] = x
}
if x := parameter.Style; x != "" {
m["style"] = x
}
if x := parameter.Explode; x != nil {
m["explode"] = x
}
if x := parameter.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := parameter.AllowReserved; x {
m["allowReserved"] = x
}
if x := parameter.Deprecated; x {
m["deprecated"] = x
}
if x := parameter.Required; x {
m["required"] = x
}
if x := parameter.Schema; x != nil {
m["schema"] = x
}
if x := parameter.Example; x != nil {
m["example"] = x
}
if x := parameter.Examples; len(x) != 0 {
m["examples"] = x
}
if x := parameter.Content; len(x) != 0 {
m["content"] = x
}
return m, nil
}
// UnmarshalJSON sets Parameter to a copy of data.
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
type ParameterBis Parameter
var x ParameterBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "in")
delete(x.Extensions, "description")
delete(x.Extensions, "style")
delete(x.Extensions, "explode")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "allowReserved")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "required")
delete(x.Extensions, "schema")
delete(x.Extensions, "example")
delete(x.Extensions, "examples")
delete(x.Extensions, "content")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*parameter = Parameter(x)
return nil
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (parameter Parameter) JSONLookup(token string) (any, error) {
switch token {
case "schema":
if parameter.Schema != nil {
if parameter.Schema.Ref != "" {
return &Ref{Ref: parameter.Schema.Ref}, nil
}
return parameter.Schema.Value, nil
}
case "name":
return parameter.Name, nil
case "in":
return parameter.In, nil
case "description":
return parameter.Description, nil
case "style":
return parameter.Style, nil
case "explode":
return parameter.Explode, nil
case "allowEmptyValue":
return parameter.AllowEmptyValue, nil
case "allowReserved":
return parameter.AllowReserved, nil
case "deprecated":
return parameter.Deprecated, nil
case "required":
return parameter.Required, nil
case "example":
return parameter.Example, nil
case "examples":
return parameter.Examples, nil
case "content":
return parameter.Content, nil
}
v, _, err := jsonpointer.GetForToken(parameter.Extensions, token)
return v, err
}
// SerializationMethod returns a parameter's serialization method.
// When a parameter's serialization method is not defined the method returns
// the default serialization method corresponding to a parameter's location.
func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) {
switch parameter.In {
case ParameterInPath, ParameterInHeader:
style := parameter.Style
if style == "" {
style = SerializationSimple
}
explode := false
if parameter.Explode != nil {
explode = *parameter.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
case ParameterInQuery, ParameterInCookie:
style := parameter.Style
if style == "" {
style = SerializationForm
}
explode := true
if parameter.Explode != nil {
explode = *parameter.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
default:
return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In)
}
}
// Validate returns an error if Parameter does not comply with the OpenAPI spec.
func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if parameter.Name == "" {
return newParameterNameRequired(parameter.Origin)
}
in := parameter.In
switch in {
case
ParameterInPath,
ParameterInQuery,
ParameterInHeader,
ParameterInCookie:
default:
return newInvalidParameterIn(parameter.In, parameter.Origin)
}
if in == ParameterInPath && !parameter.Required {
return newPathParameterRequired(parameter.Name, parameter.Origin)
}
// Validate a parameter's serialization method.
sm, err := parameter.SerializationMethod()
if err != nil {
return err
}
var smSupported bool
switch {
case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
smSupported = true
}
if !smSupported {
e := newInvalidSerializationMethod(in, sm.Style, sm.Explode, parameter.Origin)
return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "schema", Cause: e}
}
if (parameter.Schema == nil) == (len(parameter.Content) == 0) {
return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "schema",
Cause: newParameterContentSchemaExactlyOne(parameter.Origin)}
}
if content := parameter.Content; content != nil {
if len(content) > 1 {
return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "content",
Cause: newParameterContentSingleEntry(parameter.Origin)}
}
if err := content.Validate(ctx); err != nil {
return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "content", Cause: err}
}
}
if schema := parameter.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "schema", Cause: err}
}
if parameter.Example != nil && parameter.Examples != nil {
return newParameterExampleAndExamplesExclusive(parameter.Name, parameter.Origin)
}
if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
return nil
}
if example := parameter.Example; example != nil {
if err := validateExampleValue(ctx, example, schema.Value); err != nil {
return newSchemaValueError("example", err, parameter.Origin)
}
} else if examples := parameter.Examples; examples != nil {
for _, k := range componentNames(examples) {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return &ParameterExampleValidationError{ExampleName: k, Cause: err}
}
if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
return newSchemaValueError("example",
&ParameterExampleValidationError{ExampleName: k, Cause: err},
exampleValueOrigin(v.Value, parameter.Origin))
}
}
}
}
return validateExtensions(ctx, parameter.Extensions, parameter.Origin)
}
// UnmarshalJSON sets ParametersMap to a copy of data.
func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error) {
*parametersMap, err = unmarshalStringMapP[ParameterRef](data)
return
}

View File

@@ -0,0 +1,244 @@
package openapi3
import (
"context"
"encoding/json"
"fmt"
"maps"
"net/http"
)
// PathItem is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
type PathItem struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// MarshalJSON returns the JSON encoding of PathItem.
func (pathItem PathItem) MarshalJSON() ([]byte, error) {
x, err := pathItem.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of PathItem.
func (pathItem PathItem) MarshalYAML() (any, error) {
if ref := pathItem.Ref; ref != "" {
return Ref{Ref: ref}, nil
}
m := make(map[string]any, 13+len(pathItem.Extensions))
maps.Copy(m, pathItem.Extensions)
if x := pathItem.Summary; x != "" {
m["summary"] = x
}
if x := pathItem.Description; x != "" {
m["description"] = x
}
if x := pathItem.Connect; x != nil {
m["connect"] = x
}
if x := pathItem.Delete; x != nil {
m["delete"] = x
}
if x := pathItem.Get; x != nil {
m["get"] = x
}
if x := pathItem.Head; x != nil {
m["head"] = x
}
if x := pathItem.Options; x != nil {
m["options"] = x
}
if x := pathItem.Patch; x != nil {
m["patch"] = x
}
if x := pathItem.Post; x != nil {
m["post"] = x
}
if x := pathItem.Put; x != nil {
m["put"] = x
}
if x := pathItem.Trace; x != nil {
m["trace"] = x
}
if x := pathItem.Servers; len(x) != 0 {
m["servers"] = x
}
if x := pathItem.Parameters; len(x) != 0 {
m["parameters"] = x
}
return m, nil
}
// UnmarshalJSON sets PathItem to a copy of data.
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
type PathItemBis PathItem
var x PathItemBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "connect")
delete(x.Extensions, "delete")
delete(x.Extensions, "get")
delete(x.Extensions, "head")
delete(x.Extensions, "options")
delete(x.Extensions, "patch")
delete(x.Extensions, "post")
delete(x.Extensions, "put")
delete(x.Extensions, "trace")
delete(x.Extensions, "servers")
delete(x.Extensions, "parameters")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*pathItem = PathItem(x)
return nil
}
func (pathItem *PathItem) Operations() map[string]*Operation {
operations := make(map[string]*Operation)
if v := pathItem.Connect; v != nil {
operations[http.MethodConnect] = v
}
if v := pathItem.Delete; v != nil {
operations[http.MethodDelete] = v
}
if v := pathItem.Get; v != nil {
operations[http.MethodGet] = v
}
if v := pathItem.Head; v != nil {
operations[http.MethodHead] = v
}
if v := pathItem.Options; v != nil {
operations[http.MethodOptions] = v
}
if v := pathItem.Patch; v != nil {
operations[http.MethodPatch] = v
}
if v := pathItem.Post; v != nil {
operations[http.MethodPost] = v
}
if v := pathItem.Put; v != nil {
operations[http.MethodPut] = v
}
if v := pathItem.Trace; v != nil {
operations[http.MethodTrace] = v
}
return operations
}
func (pathItem *PathItem) GetOperation(method string) *Operation {
switch method {
case http.MethodConnect:
return pathItem.Connect
case http.MethodDelete:
return pathItem.Delete
case http.MethodGet:
return pathItem.Get
case http.MethodHead:
return pathItem.Head
case http.MethodOptions:
return pathItem.Options
case http.MethodPatch:
return pathItem.Patch
case http.MethodPost:
return pathItem.Post
case http.MethodPut:
return pathItem.Put
case http.MethodTrace:
return pathItem.Trace
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
switch method {
case http.MethodConnect:
pathItem.Connect = operation
case http.MethodDelete:
pathItem.Delete = operation
case http.MethodGet:
pathItem.Get = operation
case http.MethodHead:
pathItem.Head = operation
case http.MethodOptions:
pathItem.Options = operation
case http.MethodPatch:
pathItem.Patch = operation
case http.MethodPost:
pathItem.Post = operation
case http.MethodPut:
pathItem.Put = operation
case http.MethodTrace:
pathItem.Trace = operation
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
// Validate returns an error if PathItem does not comply with the OpenAPI spec.
func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
operations := pathItem.Operations()
for _, method := range componentNames(operations) {
operation := operations[method]
wrapOp := func(e error) error { return &OperationValidationError{Method: method, Cause: e} }
if err := me.emitWrapped(wrapOp, operation.Validate(ctx)); err != nil {
return err
}
}
if v := pathItem.Parameters; v != nil {
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, pathItem.Extensions, pathItem.Origin))
}
// isEmpty's introduced in 546590b1
func (pathItem *PathItem) isEmpty() bool {
// NOTE: ignores pathItem.Extensions
// NOTE: ignores pathItem.Ref
return pathItem.Summary == "" &&
pathItem.Description == "" &&
pathItem.Connect == nil &&
pathItem.Delete == nil &&
pathItem.Get == nil &&
pathItem.Head == nil &&
pathItem.Options == nil &&
pathItem.Patch == nil &&
pathItem.Post == nil &&
pathItem.Put == nil &&
pathItem.Trace == nil &&
len(pathItem.Servers) == 0 &&
len(pathItem.Parameters) == 0
}

275
vendor/github.com/getkin/kin-openapi/openapi3/paths.go generated vendored Normal file
View File

@@ -0,0 +1,275 @@
package openapi3
import (
"cmp"
"context"
"slices"
"strings"
)
// Paths is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
type Paths struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
m map[string]*PathItem
}
// NewPaths builds a paths object with path items in insertion order.
func NewPaths(opts ...NewPathsOption) *Paths {
paths := NewPathsWithCapacity(len(opts))
for _, opt := range opts {
opt(paths)
}
return paths
}
// NewPathsOption describes options to NewPaths func
type NewPathsOption func(*Paths)
// WithPath adds a named path item
func WithPath(path string, pathItem *PathItem) NewPathsOption {
return func(paths *Paths) {
if p := pathItem; p != nil && path != "" {
paths.Set(path, p)
}
}
}
// Validate returns an error if Paths does not comply with the OpenAPI spec.
func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
normalizedPaths := make(map[string]string, paths.Len())
for _, path := range paths.Keys() {
pathItem := paths.Value(path)
if path == "" || path[0] != '/' {
if err := me.emit(newPathMustStartWithSlash(path, paths.Origin)); err != nil {
return err
}
// Skip validating operations under a malformed path key: any
// findings below would be addressed under a path that has no
// resolution path until the key itself is fixed.
continue
}
if pathItem == nil {
pathItem = &PathItem{}
paths.Set(path, pathItem)
}
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
if err := me.emit(newConflictingPaths(path, oldPath, paths.Origin)); err != nil {
return err
}
// Skip validating operations under a duplicate path: the
// first occurrence already validated its operations under the
// canonical path, so re-running would surface duplicate-but-
// identical findings without new information.
continue
}
normalizedPaths[normalizedPath] = path
var commonParams []string
for _, parameterRef := range pathItem.Parameters {
if parameterRef != nil {
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
commonParams = append(commonParams, parameter.Name)
}
}
}
operations := pathItem.Operations()
for _, method := range componentNames(operations) {
operation := operations[method]
var setParams []string
for _, parameterRef := range operation.Parameters {
if parameterRef != nil {
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
setParams = append(setParams, parameter.Name)
}
}
}
if expected := len(setParams) + len(commonParams); expected != len(varsInPath) {
expected -= len(varsInPath)
if expected < 0 {
expected *= -1
}
missing := make(map[string]struct{}, expected)
definedParams := append(setParams, commonParams...)
for _, name := range definedParams {
if _, ok := varsInPath[name]; !ok {
missing[name] = struct{}{}
}
}
for _, name := range componentNames(varsInPath) {
if slices.Contains(definedParams, name) {
break
}
missing[name] = struct{}{}
}
if len(missing) != 0 {
if err := me.emit(&PathParametersError{
Path: path,
Method: method,
Missing: componentNames(missing),
Origin: pathItem.Origin,
}); err != nil {
return err
}
}
}
}
wrapPath := func(e error) error { return &PathValidationError{Path: path, Cause: e} }
if err := me.emitWrapped(wrapPath, pathItem.Validate(ctx)); err != nil {
return err
}
}
if err := me.emit(paths.validateUniqueOperationIDs()); err != nil {
return err
}
return me.finalize(validateExtensions(ctx, paths.Extensions, paths.Origin))
}
// InMatchingOrder returns paths in the order they are matched against URLs.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
// When matching URLs, concrete (non-templated) paths would be matched
// before their templated counterparts.
func (paths *Paths) InMatchingOrder() []string {
// NOTE: sorting by number of variables ASC then by descending lexicographical
// order seems to be a good heuristic.
if paths.Len() == 0 {
return nil
}
vars := make(map[int][]string)
max := 0
for path := range paths.Map() {
count := strings.Count(path, "}")
vars[count] = append(vars[count], path)
if count > max {
max = count
}
}
ordered := make([]string, 0, paths.Len())
for c := 0; c <= max; c++ {
if ps, ok := vars[c]; ok {
slices.SortFunc(ps, func(a, b string) int { return cmp.Compare(b, a) })
ordered = append(ordered, ps...)
}
}
return ordered
}
// Find returns a path that matches the key.
//
// The method ignores differences in template variable names (except possible "*" suffix).
//
// For example:
//
// paths := openapi3.Paths {
// "/person/{personName}": &openapi3.PathItem{},
// }
// pathItem := path.Find("/person/{name}")
//
// would return the correct path item.
func (paths *Paths) Find(key string) *PathItem {
// Try directly access the map
pathItem := paths.Value(key)
if pathItem != nil {
return pathItem
}
normalizedPath, expected, _ := normalizeTemplatedPath(key)
pathsMap := paths.Map()
for _, path := range componentNames(pathsMap) {
pathNormalized, got, _ := normalizeTemplatedPath(path)
if got == expected && pathNormalized == normalizedPath {
return pathsMap[path]
}
}
return nil
}
func (paths *Paths) validateUniqueOperationIDs() error {
operationIDs := make(map[string]string)
pathsMap := paths.Map()
for _, urlPath := range componentNames(pathsMap) {
pathItem := pathsMap[urlPath]
if pathItem == nil {
continue
}
operations := pathItem.Operations()
for _, httpMethod := range componentNames(operations) {
operation := operations[httpMethod]
if operation == nil || operation.OperationID == "" {
continue
}
endpoint := httpMethod + " " + urlPath
if endpointDup, ok := operationIDs[operation.OperationID]; ok {
if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests.
endpoint, endpointDup = endpointDup, endpoint
}
return newDuplicateOperationID(endpoint, endpointDup, operation.OperationID, operation.Origin)
}
operationIDs[operation.OperationID] = endpoint
}
}
return nil
}
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
if strings.IndexByte(path, '{') < 0 {
return path, 0, nil
}
var buffTpl strings.Builder
buffTpl.Grow(len(path))
var (
cc rune
count uint
isVariable bool
vars = make(map[string]struct{})
buffVar strings.Builder
)
for i, c := range path {
if isVariable {
if c == '}' {
// End path variable
isVariable = false
vars[buffVar.String()] = struct{}{}
buffVar = strings.Builder{}
// First append possible '*' before this character
// The character '}' will be appended
if i > 0 && cc == '*' {
buffTpl.WriteRune(cc)
}
} else {
buffVar.WriteRune(c)
continue
}
} else if c == '{' {
// Begin path variable
isVariable = true
// The character '{' will be appended
count++
}
// Append the character
buffTpl.WriteRune(c)
cc = c
}
return buffTpl.String(), count, vars
}

42
vendor/github.com/getkin/kin-openapi/openapi3/ref.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
//go:generate go run refsgenerator.go
// Ref is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
}
// MarshalYAML returns the YAML encoding of Ref.
func (x Ref) MarshalYAML() (any, error) {
m := make(map[string]any, 1+len(x.Extensions))
maps.Copy(m, x.Extensions)
if x := x.Ref; x != "" {
m["$ref"] = x
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Ref.
func (x Ref) MarshalJSON() ([]byte, error) {
y, err := x.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(y)
}
// Validate returns an error if Extensions does not comply with the OpenAPI spec.
func (e *Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, e.Extensions, e.Origin)
}

1310
vendor/github.com/getkin/kin-openapi/openapi3/refs.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

183
vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl generated vendored Normal file
View File

@@ -0,0 +1,183 @@
// Code generated by go generate using refs.tmpl; DO NOT EDIT refs.go.
package {{ .Package }}
import (
"context"
"encoding/json"
"net/url"
"strings"
"github.com/go-openapi/jsonpointer"
)
{{ range $type := .Types }}
// {{ $type.Name }}Ref represents either a {{ $type.Name }} or a $ref to a {{ $type.Name }}.
// When serializing and both fields are set, Ref is preferred over Value.
type {{ $type.Name }}Ref struct {
// Extensions only captures fields starting with 'x-' as no other fields
// are allowed by the openapi spec.
Extensions map[string]any
Origin *Origin `json:"-" yaml:"-"`
Ref string
Value *{{ $type.Name }}
extra []string
{{- if eq $type.Name "Schema" }}
// sibling holds keyword siblings of a $ref (OAS 3.1 / JSON Schema 2020-12).
// It is populated during unmarshal and applied to Value after $ref resolution.
sibling *Schema
{{- end }}
refPath *url.URL
}
var _ jsonpointer.JSONPointable = (*{{ $type.Name }}Ref)(nil)
func (x *{{ $type.Name }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// RefString returns the $ref value.
func (x *{{ $type.Name }}Ref) RefString() string { return x.Ref }
// CollectionName returns the JSON string used for a collection of these components.
func (x *{{ $type.Name }}Ref) CollectionName() string { return "{{ $type.CollectionName }}" }
// RefPath returns the path of the $ref relative to the root document.
func (x *{{ $type.Name }}Ref) RefPath() *url.URL { return copyURI(x.refPath) }
func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) {
// Once the refPath is set don't override. References can be loaded
// multiple times not all with access to the correct path info.
if x.refPath != nil {
return
}
x.refPath = copyURI(u)
}
// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref.
func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref, Extensions: x.Extensions}, nil
}
return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of {{ $type.Name }}Ref.
func (x {{ $type.Name }}Ref) MarshalJSON() ([]byte, error) {
y, err := x.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(y)
}
// UnmarshalJSON sets {{ $type.Name }}Ref to a copy of data.
func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error {
var refOnly Ref
if err := json.Unmarshal(data, &refOnly); err == nil && refOnly.Ref != "" {
extra := map[string]any{}
_ = json.Unmarshal(data, &extra)
delete(extra, "$ref")
x.Ref = refOnly.Ref
x.Origin = refOnly.Origin
if len(extra) != 0 {
x.extra = componentNames(extra)
{{- if eq $type.Name "Schema" }}
// OAS 3.1 / JSON Schema 2020-12: sibling keywords alongside $ref are valid
// and must be merged with the resolved reference. Parse the full object so
// the sibling fields are available after $ref resolution in resolveSchemaRef.
hasSiblings := false
for k := range extra {
if !strings.HasPrefix(k, "x-") {
hasSiblings = true
break
}
}
if hasSiblings {
var sibling Schema
if err := json.Unmarshal(data, &sibling); err == nil {
x.sibling = &sibling
}
}
{{- end }}
for k := range extra {
if !strings.HasPrefix(k, "x-") {
delete(extra, k)
}
}
if len(extra) != 0 {
x.Extensions = extra
}
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// validateExtras returns an error if {{ $type.Name }}Ref has sibling fields
// alongside $ref that are not allowed by the validation options.
func (x *{{ $type.Name }}Ref) validateExtras(ctx context.Context) error {
validationOpts := getValidationOptions(ctx)
var extras []string
allowed := validationOpts.extraSiblingFieldsAllowed
if allowed == nil {
allowed = make(map[string]struct{})
}
for _, ex := range x.extra {
if _, ok := allowed[ex]; !ok {
if _, ok := x.Extensions[ex]; !ok {
extras = append(extras, ex)
}
// extras in the Extensions checked below
}
}
if validationOpts.schemaExtensionsInRefProhibited {
for _, ex := range componentNames(x.Extensions) {
if _, ok := allowed[ex]; !ok {
extras = append(extras, ex)
}
// extras in the Extensions checked below
}
}
if len(extras) != 0 {
{{- if eq $type.Name "Schema" }}
if !validationOpts.isOpenAPI31OrLater {
return newExtraSiblingFields(extras, x.Origin)
}
{{- else }}
return newExtraSiblingFields(extras, x.Origin)
{{- end }}
}
return nil
}
// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec.
func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if err := x.validateExtras(ctx); err != nil {
return err
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return newUnresolvedRef(x.Ref, x.Origin)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *{{ $type.Name }}Ref) JSONLookup(token string) (any, error) {
if token == "$ref" {
return x.Ref, nil
}
if v, ok := x.Extensions[token]; ok {
return v, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
{{ end -}}

View File

@@ -0,0 +1,62 @@
// Code generated by go generate; DO NOT EDIT.
package {{ .Package }}_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
{{ range $type := .Types }}
func Test{{ $type.Name }}Ref_Extensions(t *testing.T) {
data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`)
expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`)
ref := openapi3.{{ $type.Name }}Ref{}
err := json.Unmarshal(data, &ref)
assert.NoError(t, err)
// captures extension
assert.Equal(t, "#/components/schemas/Pet", ref.Ref)
assert.Equal(t, float64(1), ref.Extensions["x-order"])
// does not capture non-extensions
assert.Nil(t, ref.Extensions["something"])
// validation
err = ref.Validate(t.Context())
require.EqualError(t, err, "extra sibling fields: [something]")
err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef())
require.EqualError(t, err, "extra sibling fields: [something x-order]")
err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something"))
assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined
// Verify round trip JSON
// Compare as string to make error message more readable if different
outJson, err := ref.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, string(outJson), string(expectMarshalJson), "MarshalJSON output is not the same as input data")
// non-extension not json lookable
_, err = ref.JSONLookup("something")
assert.Error(t, err)
{{ if ne $type.Name "Header" }}
t.Run("extentions in value", func(t *testing.T) {
ref.Value = &openapi3.{{ $type.Name }}{Extensions: map[string]any{}}
ref.Value.Extensions["x-order"] = 2.0
// prefers the value next to the \$ref over the one in the \$ref.
v, err := ref.JSONLookup("x-order")
assert.NoError(t, err)
assert.Equal(t, float64(1), v)
})
{{ else }}
// Header does not have its own extensions.
{{ end -}}
}
{{ end -}}

View File

@@ -0,0 +1,143 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
type RequestBody struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Content Content `json:"content" yaml:"content"`
}
func NewRequestBody() *RequestBody {
return &RequestBody{}
}
func (requestBody *RequestBody) WithDescription(value string) *RequestBody {
requestBody.Description = value
return requestBody
}
func (requestBody *RequestBody) WithRequired(value bool) *RequestBody {
requestBody.Required = value
return requestBody
}
func (requestBody *RequestBody) WithContent(content Content) *RequestBody {
requestBody.Content = content
return requestBody
}
func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []string) *RequestBody {
requestBody.Content = NewContentWithSchemaRef(value, consumes)
return requestBody
}
func (requestBody *RequestBody) WithSchema(value *Schema, consumes []string) *RequestBody {
requestBody.Content = NewContentWithSchema(value, consumes)
return requestBody
}
func (requestBody *RequestBody) WithJSONSchemaRef(value *SchemaRef) *RequestBody {
requestBody.Content = NewContentWithJSONSchemaRef(value)
return requestBody
}
func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody {
requestBody.Content = NewContentWithJSONSchema(value)
return requestBody
}
func (requestBody *RequestBody) WithFormDataSchemaRef(value *SchemaRef) *RequestBody {
requestBody.Content = NewContentWithFormDataSchemaRef(value)
return requestBody
}
func (requestBody *RequestBody) WithFormDataSchema(value *Schema) *RequestBody {
requestBody.Content = NewContentWithFormDataSchema(value)
return requestBody
}
func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType {
m := requestBody.Content
if m == nil {
return nil
}
return m[mediaType]
}
// MarshalJSON returns the JSON encoding of RequestBody.
func (requestBody RequestBody) MarshalJSON() ([]byte, error) {
x, err := requestBody.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of RequestBody.
func (requestBody RequestBody) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(requestBody.Extensions))
maps.Copy(m, requestBody.Extensions)
if x := requestBody.Description; x != "" {
m["description"] = requestBody.Description
}
if x := requestBody.Required; x {
m["required"] = x
}
if x := requestBody.Content; true {
m["content"] = x
}
return m, nil
}
// UnmarshalJSON sets RequestBody to a copy of data.
func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
type RequestBodyBis RequestBody
var x RequestBodyBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "description")
delete(x.Extensions, "required")
delete(x.Extensions, "content")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*requestBody = RequestBody(x)
return nil
}
// Validate returns an error if RequestBody does not comply with the OpenAPI spec.
func (requestBody *RequestBody) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if requestBody.Content == nil {
return newRequestBodyContentRequired(requestBody.Origin)
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
}
if err := requestBody.Content.Validate(ctx); err != nil {
return err
}
return validateExtensions(ctx, requestBody.Extensions, requestBody.Origin)
}
// UnmarshalJSON sets RequestBodies to a copy of data.
func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error) {
*requestBodies, err = unmarshalStringMapP[RequestBodyRef](data)
return
}

View File

@@ -0,0 +1,219 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
"strconv"
)
// Responses is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
type Responses struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
m map[string]*ResponseRef
}
// NewResponses builds a responses object with response objects in insertion order.
// Given no arguments, NewResponses returns an empty responses object.
func NewResponses(opts ...NewResponsesOption) *Responses {
responses := NewResponsesWithCapacity(len(opts))
for _, opt := range opts {
opt(responses)
}
return responses
}
// NewResponsesOption describes options to NewResponses func
type NewResponsesOption func(*Responses)
// WithStatus adds a status code keyed ResponseRef
func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption {
return func(responses *Responses) {
if r := responseRef; r != nil {
code := strconv.FormatInt(int64(status), 10)
responses.Set(code, r)
}
}
}
// WithName adds a name-keyed Response
func WithName(name string, response *Response) NewResponsesOption {
return func(responses *Responses) {
if r := response; r != nil && name != "" {
responses.Set(name, &ResponseRef{Value: r})
}
}
}
// Default returns the default response
func (responses *Responses) Default() *ResponseRef {
return responses.Value("default")
}
// Status returns a ResponseRef for the given status
// If an exact match isn't initially found a patterned field is checked using
// the first digit to determine the range (eg: 201 to 2XX)
// See https://spec.openapis.org/oas/v3.0.3#patterned-fields-0
func (responses *Responses) Status(status int) *ResponseRef {
st := strconv.FormatInt(int64(status), 10)
if rref := responses.Value(st); rref != nil {
return rref
}
if 99 < status && status < 600 {
st = string(st[0]) + "XX"
switch st {
case "1XX", "2XX", "3XX", "4XX", "5XX":
return responses.Value(st)
}
}
return nil
}
// Validate returns an error if Responses does not comply with the OpenAPI spec.
func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if responses.Len() == 0 {
if err := me.emit(newResponsesNonEmptyRequired(responses.Origin)); err != nil {
return err
}
// Fall through so validateExtensions still runs and any extension
// errors aggregate with the empty-responses finding under multi mode.
}
for _, key := range responses.Keys() {
v := responses.Value(key)
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, responses.Extensions, responses.Origin))
}
// Response is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
type Response struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
}
func NewResponse() *Response {
return &Response{}
}
func (response *Response) WithDescription(value string) *Response {
response.Description = &value
return response
}
func (response *Response) WithContent(content Content) *Response {
response.Content = content
return response
}
func (response *Response) WithJSONSchema(schema *Schema) *Response {
response.Content = NewContentWithJSONSchema(schema)
return response
}
func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response {
response.Content = NewContentWithJSONSchemaRef(schema)
return response
}
// MarshalJSON returns the JSON encoding of Response.
func (response Response) MarshalJSON() ([]byte, error) {
x, err := response.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Response.
func (response Response) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(response.Extensions))
maps.Copy(m, response.Extensions)
if x := response.Description; x != nil {
m["description"] = x
}
if x := response.Headers; len(x) != 0 {
m["headers"] = x
}
if x := response.Content; len(x) != 0 {
m["content"] = x
}
if x := response.Links; len(x) != 0 {
m["links"] = x
}
return m, nil
}
// UnmarshalJSON sets Response to a copy of data.
func (response *Response) UnmarshalJSON(data []byte) error {
type ResponseBis Response
var x ResponseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "description")
delete(x.Extensions, "headers")
delete(x.Extensions, "content")
delete(x.Extensions, "links")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*response = Response(x)
return nil
}
// Validate returns an error if Response does not comply with the OpenAPI spec.
func (response *Response) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if response.Description == nil {
return newResponseDescriptionRequired(response.Origin)
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true
}
if content := response.Content; content != nil {
if err := content.Validate(ctx); err != nil {
return err
}
}
for _, name := range componentNames(response.Headers) {
header := response.Headers[name]
if err := header.Validate(ctx); err != nil {
return err
}
}
for _, name := range componentNames(response.Links) {
link := response.Links[name]
if err := link.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, response.Extensions, response.Origin)
}
// UnmarshalJSON sets ResponseBodies to a copy of data.
func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) {
*responseBodies, err = unmarshalStringMapP[ResponseRef](data)
return
}

3182
vendor/github.com/getkin/kin-openapi/openapi3/schema.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,172 @@
package openapi3
import (
"fmt"
"math"
"net/netip"
"regexp"
)
// FormatValidator is an interface for custom format validators.
type FormatValidator[T any] interface {
Validate(value T) error
}
// StringFormatValidator is a type alias for FormatValidator[string]
type StringFormatValidator = FormatValidator[string]
// NumberFormatValidator is a type alias for FormatValidator[float64]
type NumberFormatValidator = FormatValidator[float64]
// IntegerFormatValidator is a type alias for FormatValidator[int64]
type IntegerFormatValidator = FormatValidator[int64]
var (
// SchemaStringFormats is a map of custom string format validators.
SchemaStringFormats = make(map[string]StringFormatValidator)
// SchemaNumberFormats is a map of custom number format validators.
SchemaNumberFormats = make(map[string]NumberFormatValidator)
// SchemaIntegerFormats is a map of custom integer format validators.
SchemaIntegerFormats = make(map[string]IntegerFormatValidator)
)
const (
// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$`
// FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses.
// Use DefineStringFormat(...) if you need something stricter.
FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$`
// FormatOfStringByte is a regexp for base64-encoded characters, for example, "U3dhZ2dlciByb2Nrcw=="
FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)`
// FormatOfStringDate is a RFC3339 date format regexp, for example "2017-07-21".
FormatOfStringDate = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$`
// FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z".
FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$`
)
func init() {
DefineStringFormatValidator("byte", NewRegexpFormatValidator(FormatOfStringByte))
DefineStringFormatValidator("date", NewRegexpFormatValidator(FormatOfStringDate))
DefineStringFormatValidator("date-time", NewRegexpFormatValidator(FormatOfStringDateTime))
DefineIntegerFormatValidator("int32", NewRangeFormatValidator(int64(math.MinInt32), int64(math.MaxInt32)))
DefineIntegerFormatValidator("int64", NewRangeFormatValidator(int64(math.MinInt64), int64(math.MaxInt64)))
}
// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
func DefineIPv4Format() {
DefineStringFormatValidator("ipv4", NewIPValidator(true))
}
// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec
func DefineIPv6Format() {
DefineStringFormatValidator("ipv6", NewIPValidator(false))
}
type stringRegexpFormatValidator struct {
re *regexp.Regexp
}
func (s stringRegexpFormatValidator) Validate(value string) error {
if !s.re.MatchString(value) {
return fmt.Errorf(`string doesn't match pattern "%s"`, s.re.String())
}
return nil
}
type callbackValidator[T any] struct {
fn func(T) error
}
func (c callbackValidator[T]) Validate(value T) error {
return c.fn(value)
}
type rangeFormat[T int64 | float64] struct {
min, max T
}
func (r rangeFormat[T]) Validate(value T) error {
if value < r.min || value > r.max {
return fmt.Errorf("value should be between %v and %v", r.min, r.max)
}
return nil
}
// NewRangeFormatValidator creates a new FormatValidator that validates the value is within a given range.
func NewRangeFormatValidator[T int64 | float64](min, max T) FormatValidator[T] {
return rangeFormat[T]{min: min, max: max}
}
// NewRegexpFormatValidator creates a new FormatValidator that uses a regular expression to validate the value.
func NewRegexpFormatValidator(pattern string) StringFormatValidator {
re, err := regexp.Compile(pattern)
if err != nil {
err := fmt.Errorf("string regexp format has invalid pattern %q: %w", pattern, err)
panic(err)
}
return stringRegexpFormatValidator{re: re}
}
// NewCallbackValidator creates a new FormatValidator that uses a callback function to validate the value.
func NewCallbackValidator[T any](fn func(T) error) FormatValidator[T] {
return callbackValidator[T]{fn: fn}
}
// DefineStringFormatValidator defines a custom format validator for a given string format.
func DefineStringFormatValidator(name string, validator StringFormatValidator) {
SchemaStringFormats[name] = validator
}
// DefineNumberFormatValidator defines a custom format validator for a given number format.
func DefineNumberFormatValidator(name string, validator NumberFormatValidator) {
SchemaNumberFormats[name] = validator
}
// DefineIntegerFormatValidator defines a custom format validator for a given integer format.
func DefineIntegerFormatValidator(name string, validator IntegerFormatValidator) {
SchemaIntegerFormats[name] = validator
}
// DefineStringFormat defines a regexp pattern for a given string format
//
// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern)) instead.
func DefineStringFormat(name string, pattern string) {
DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern))
}
// DefineStringFormatCallback defines a callback function for a given string format
//
// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewCallbackValidator(fn)) instead.
func DefineStringFormatCallback(name string, callback func(string) error) {
DefineStringFormatValidator(name, NewCallbackValidator(callback))
}
// NewIPValidator creates a new FormatValidator that validates the value is an IP address.
func NewIPValidator(isIPv4 bool) FormatValidator[string] {
return callbackValidator[string]{fn: func(ip string) error {
addr, err := netip.ParseAddr(ip)
if err != nil {
return &SchemaError{
Value: ip,
Reason: "Not an IP address",
}
}
if isIPv4 && !addr.Is4() {
return &SchemaError{
Value: ip,
Reason: "Not an IPv4 address (it's IPv6)",
}
}
if !isIPv4 && !addr.Is6() {
return &SchemaError{
Value: ip,
Reason: "Not an IPv6 address (it's IPv4)",
}
}
return nil
}}
}

View File

@@ -0,0 +1,212 @@
package openapi3
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/santhosh-tekuri/jsonschema/v6"
)
// jsonSchemaValidator wraps the santhosh-tekuri/jsonschema validator
type jsonSchemaValidator struct {
compiler *jsonschema.Compiler
schema *jsonschema.Schema
}
// newJSONSchemaValidator creates a new validator using JSON Schema 2020-12
func newJSONSchemaValidator(schema *Schema) (*jsonSchemaValidator, error) {
// Convert OpenAPI Schema to JSON Schema format
schemaBytes, err := json.Marshal(schema)
if err != nil {
return nil, fmt.Errorf("failed to marshal schema: %w", err)
}
var schemaMap map[string]any
if err := json.Unmarshal(schemaBytes, &schemaMap); err != nil {
return nil, fmt.Errorf("failed to unmarshal schema: %w", err)
}
// OpenAPI 3.1 specific transformations
transformOpenAPIToJSONSchema(schemaMap)
// Create compiler
compiler := jsonschema.NewCompiler()
compiler.DefaultDraft(jsonschema.Draft2020)
// Add the schema
schemaURL := "https://example.com/schema.json"
if err := compiler.AddResource(schemaURL, schemaMap); err != nil {
return nil, fmt.Errorf("failed to add schema resource: %w", err)
}
// Compile the schema
compiledSchema, err := compiler.Compile(schemaURL)
if err != nil {
return nil, fmt.Errorf("failed to compile schema: %w", err)
}
return &jsonSchemaValidator{
compiler: compiler,
schema: compiledSchema,
}, nil
}
// transformOpenAPIToJSONSchema converts OpenAPI 3.0/3.1 specific keywords to JSON Schema format
func transformOpenAPIToJSONSchema(schema map[string]any) {
// Handle nullable - in OpenAPI 3.0, nullable is a boolean flag
// In OpenAPI 3.1 / JSON Schema 2020-12, we use type arrays
if nullable, ok := schema["nullable"].(bool); ok && nullable {
if typeVal, ok := schema["type"].(string); ok {
// Convert to type array with null
schema["type"] = []string{typeVal, "null"}
} else if _, hasType := schema["type"]; !hasType {
// nullable: true without type - add "null" to allow null values
schema["type"] = []string{"null"}
}
delete(schema, "nullable")
}
// Handle exclusiveMinimum/exclusiveMaximum
// In OpenAPI 3.0, these are booleans alongside minimum/maximum
// In JSON Schema 2020-12, they are numeric values
if exclusiveMin, ok := schema["exclusiveMinimum"].(bool); ok {
if exclusiveMin {
if schemaMin, ok := schema["minimum"].(float64); ok {
schema["exclusiveMinimum"] = schemaMin
delete(schema, "minimum")
} else {
delete(schema, "exclusiveMinimum")
}
} else {
// exclusiveMinimum: false means inclusive, which is the JSON Schema default
delete(schema, "exclusiveMinimum")
}
}
if exclusiveMax, ok := schema["exclusiveMaximum"].(bool); ok {
if exclusiveMax {
if schemaMax, ok := schema["maximum"].(float64); ok {
schema["exclusiveMaximum"] = schemaMax
delete(schema, "maximum")
} else {
delete(schema, "exclusiveMaximum")
}
} else {
// exclusiveMaximum: false means inclusive, which is the JSON Schema default
delete(schema, "exclusiveMaximum")
}
}
// Remove OpenAPI-specific keywords that aren't in JSON Schema
delete(schema, "discriminator")
delete(schema, "xml")
delete(schema, "externalDocs")
delete(schema, "example") // Use "examples" in 2020-12
// Recursively transform nested schemas (single schema fields)
for _, key := range []string{
"additionalProperties", "items", "not",
// OpenAPI 3.1 / JSON Schema 2020-12 fields
"contains", "propertyNames", "unevaluatedItems", "unevaluatedProperties",
"if", "then", "else", "contentSchema",
} {
if val, ok := schema[key]; ok {
if nestedSchema, ok := val.(map[string]any); ok {
transformOpenAPIToJSONSchema(nestedSchema)
}
}
}
// Transform schema arrays (oneOf, anyOf, allOf, prefixItems)
for _, key := range []string{"oneOf", "anyOf", "allOf", "prefixItems"} {
if val, ok := schema[key].([]any); ok {
for _, item := range val {
if nestedSchema, ok := item.(map[string]any); ok {
transformOpenAPIToJSONSchema(nestedSchema)
}
}
}
}
// Transform schema maps (properties, patternProperties, dependentSchemas, $defs)
for _, key := range []string{"properties", "patternProperties", "dependentSchemas", "$defs"} {
if props, ok := schema[key].(map[string]any); ok {
for _, propVal := range props {
if propSchema, ok := propVal.(map[string]any); ok {
transformOpenAPIToJSONSchema(propSchema)
}
}
}
}
}
// validate validates a value against the compiled JSON Schema
func (v *jsonSchemaValidator) validate(value any) error {
if err := v.schema.Validate(value); err != nil {
// Convert jsonschema error to SchemaError
return convertJSONSchemaError(err)
}
return nil
}
// convertJSONSchemaError converts a jsonschema validation error to OpenAPI SchemaError format
func convertJSONSchemaError(err error) error {
// TODO: Go 1.26
// if err, ok := errors.AsType[*jsonschema.ValidationError](err); ok {
// return formatValidationError(err, "")
var validationErr *jsonschema.ValidationError
if errors.As(err, &validationErr) {
return formatValidationError(validationErr, "")
}
return err
}
// formatValidationError recursively formats validation errors
func formatValidationError(verr *jsonschema.ValidationError, parentPath string) error {
// Build the path from InstanceLocation slice
path := "/" + strings.Join(verr.InstanceLocation, "/")
if parentPath != "" && path != "/" {
path = parentPath + path
} else if path == "/" {
path = parentPath
}
// Build error message using the Error() method
var msg strings.Builder
if path != "" {
fmt.Fprintf(&msg, `error at "%s": `, path)
}
msg.WriteString(verr.Error())
// If there are sub-errors, format them too
if len(verr.Causes) > 0 {
var subErrors MultiError
for _, cause := range verr.Causes {
if subErr := formatValidationError(cause, path); subErr != nil {
subErrors = append(subErrors, subErr)
}
}
if len(subErrors) > 0 {
return &SchemaError{
Reason: msg.String(),
Origin: fmt.Errorf("validation failed due to: %w", subErrors),
}
}
}
return &SchemaError{
Reason: msg.String(),
}
}
// useJSONSchema2020 validates using the JSON Schema 2020-12 validator
func (schema *Schema) useJSONSchema2020(settings *schemaValidationSettings, value any) error {
validator, err := newJSONSchemaValidator(schema)
if err != nil {
// Fall back to built-in validator if compilation fails
return schema.visitJSON(settings, value)
}
return validator.validate(value)
}

View File

@@ -0,0 +1,36 @@
package openapi3
import (
"fmt"
"regexp"
)
var patRewriteCodepoints = regexp.MustCompile(`(?P<replaced_with_slash_x>\\u)(?P<code>[0-9A-F]{4})`)
// See https://pkg.go.dev/regexp/syntax
func intoGoRegexp(re string) string {
return patRewriteCodepoints.ReplaceAllString(re, `\x{${code}}`)
}
// NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns]
func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err error) {
pattern := schema.Pattern
if c != nil {
cp, err = c(pattern)
} else {
cp, err = regexp.Compile(intoGoRegexp(pattern))
}
if err != nil {
schemaErr := &SchemaError{
Schema: schema,
SchemaField: "pattern",
Origin: err,
Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err),
}
err = newSchemaPatternRegexError(pattern, schemaErr, schema.Origin)
return
}
compiledPatterns.Store(pattern, cp)
return
}

View File

@@ -0,0 +1,168 @@
package openapi3
import (
"sync"
)
// SchemaValidationOption describes options a user has when validating request / response bodies.
type SchemaValidationOption func(*schemaValidationSettings)
type RegexCompilerFunc func(expr string) (RegexMatcher, error)
type RegexMatcher interface {
MatchString(s string) bool
}
type schemaValidationSettings struct {
failfast bool
multiError bool
asreq, asrep bool // exclusive (XOR) fields
formatValidationEnabled bool
patternValidationDisabled bool
readOnlyValidationDisabled bool
writeOnlyValidationDisabled bool
useJSONSchema2020 bool // Use JSON Schema 2020-12 validator for OpenAPI 3.1
regexCompiler RegexCompilerFunc
onceSettingDefaults sync.Once
defaultsSet func()
customizeMessageError func(err *SchemaError) string
// Per-validation format validators (checked before global ones)
stringFormats map[string]StringFormatValidator
numberFormats map[string]NumberFormatValidator
integerFormats map[string]IntegerFormatValidator
}
// FailFast returns schema validation errors quicker.
func FailFast() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.failfast = true }
}
func MultiErrors() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.multiError = true }
}
func VisitAsRequest() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false }
}
func VisitAsResponse() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true }
}
// EnableFormatValidation setting makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
func EnableFormatValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.formatValidationEnabled = true }
}
// DisablePatternValidation setting makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisablePatternValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.patternValidationDisabled = true }
}
// DisableReadOnlyValidation setting makes Validate not return an error when validating properties marked as read-only
func DisableReadOnlyValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.readOnlyValidationDisabled = true }
}
// DisableWriteOnlyValidation setting makes Validate not return an error when validating properties marked as write-only
func DisableWriteOnlyValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.writeOnlyValidationDisabled = true }
}
// DefaultsSet executes the given callback (once) IFF schema validation set default values.
func DefaultsSet(f func()) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.defaultsSet = f }
}
// SetSchemaErrorMessageCustomizer allows to override the schema error message.
// If the passed function returns an empty string, it returns to the previous Error() implementation.
func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.customizeMessageError = f }
}
// SetSchemaRegexCompiler allows to override the regex implementation used to validate field "pattern".
func SetSchemaRegexCompiler(c RegexCompilerFunc) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.regexCompiler = c }
}
// WithStringFormatValidators adds per-validation string format validators.
// These validators are checked before global SchemaStringFormats and allow
// different validations for the same format name across different specs.
func WithStringFormatValidators(validators map[string]StringFormatValidator) SchemaValidationOption {
return func(s *schemaValidationSettings) {
s.stringFormats = validators
}
}
// WithStringFormatValidator adds a single per-validation string format validator.
// This validator is checked before global SchemaStringFormats and allows
// different validations for the same format name across different specs.
func WithStringFormatValidator(name string, validator StringFormatValidator) SchemaValidationOption {
return func(s *schemaValidationSettings) {
if s.stringFormats == nil {
s.stringFormats = make(map[string]StringFormatValidator)
}
s.stringFormats[name] = validator
}
}
// WithNumberFormatValidators adds per-validation number format validators.
// These validators are checked before global SchemaNumberFormats and allow
// different validations for the same format name across different specs.
func WithNumberFormatValidators(validators map[string]NumberFormatValidator) SchemaValidationOption {
return func(s *schemaValidationSettings) {
s.numberFormats = validators
}
}
// WithNumberFormatValidator adds a single per-validation number format validator.
// This validator is checked before global SchemaNumberFormats and allows
// different validations for the same format name across different specs.
func WithNumberFormatValidator(name string, validator NumberFormatValidator) SchemaValidationOption {
return func(s *schemaValidationSettings) {
if s.numberFormats == nil {
s.numberFormats = make(map[string]NumberFormatValidator)
}
s.numberFormats[name] = validator
}
}
// WithIntegerFormatValidators adds per-validation integer format validators.
// These validators are checked before global SchemaIntegerFormats and allow
// different validations for the same format name across different specs.
func WithIntegerFormatValidators(validators map[string]IntegerFormatValidator) SchemaValidationOption {
return func(s *schemaValidationSettings) {
s.integerFormats = validators
}
}
// WithIntegerFormatValidator adds a single per-validation integer format validator.
// This validator is checked before global SchemaIntegerFormats and allows
// different validations for the same format name across different specs.
func WithIntegerFormatValidator(name string, validator IntegerFormatValidator) SchemaValidationOption {
return func(s *schemaValidationSettings) {
if s.integerFormats == nil {
s.integerFormats = make(map[string]IntegerFormatValidator)
}
s.integerFormats[name] = validator
}
}
// EnableJSONSchema2020 enables JSON Schema 2020-12 compliant validation.
// This enables support for OpenAPI 3.1 and JSON Schema 2020-12 features.
// When enabled, validation uses the jsonschema library instead of the built-in validator.
func EnableJSONSchema2020() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.useJSONSchema2020 = true }
}
func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
settings := &schemaValidationSettings{}
for _, opt := range opts {
opt(settings)
}
return settings
}

View File

@@ -0,0 +1,57 @@
package openapi3
import (
"context"
)
type SecurityRequirements []SecurityRequirement
func NewSecurityRequirements() *SecurityRequirements {
return &SecurityRequirements{}
}
func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements {
*srs = append(*srs, securityRequirement)
return srs
}
// Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec.
func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, security := range srs {
if err := security.Validate(ctx); err != nil {
return err
}
}
return nil
}
// SecurityRequirement is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object
type SecurityRequirement map[string][]string
func NewSecurityRequirement() SecurityRequirement {
return make(SecurityRequirement)
}
func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement {
if len(scopes) == 0 {
scopes = []string{} // Forces the variable to be encoded as an array instead of null
}
security[provider] = scopes
return security
}
// Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec.
func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error {
// ctx = WithValidationOptions(ctx, opts...)
return nil
}
// UnmarshalJSON sets SecurityRequirement to a copy of data.
func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) {
*security, err = unmarshalStringMap[[]string](data)
return
}

View File

@@ -0,0 +1,434 @@
package openapi3
import (
"context"
"encoding/json"
"fmt"
"maps"
"net/url"
"slices"
)
// SecurityScheme is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object
type SecurityScheme struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
}
func NewSecurityScheme() *SecurityScheme {
return &SecurityScheme{}
}
func NewCSRFSecurityScheme() *SecurityScheme {
return &SecurityScheme{
Type: "apiKey",
In: "header",
Name: "X-XSRF-TOKEN",
}
}
func NewOIDCSecurityScheme(oidcUrl string) *SecurityScheme {
return &SecurityScheme{
Type: "openIdConnect",
OpenIdConnectUrl: oidcUrl,
}
}
func NewJWTSecurityScheme() *SecurityScheme {
return &SecurityScheme{
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
}
}
// MarshalJSON returns the JSON encoding of SecurityScheme.
func (ss SecurityScheme) MarshalJSON() ([]byte, error) {
x, err := ss.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of SecurityScheme.
func (ss SecurityScheme) MarshalYAML() (any, error) {
m := make(map[string]any, 8+len(ss.Extensions))
maps.Copy(m, ss.Extensions)
if x := ss.Type; x != "" {
m["type"] = x
}
if x := ss.Description; x != "" {
m["description"] = x
}
if x := ss.Name; x != "" {
m["name"] = x
}
if x := ss.In; x != "" {
m["in"] = x
}
if x := ss.Scheme; x != "" {
m["scheme"] = x
}
if x := ss.BearerFormat; x != "" {
m["bearerFormat"] = x
}
if x := ss.Flows; x != nil {
m["flows"] = x
}
if x := ss.OpenIdConnectUrl; x != "" {
m["openIdConnectUrl"] = x
}
return m, nil
}
// UnmarshalJSON sets SecurityScheme to a copy of data.
func (ss *SecurityScheme) UnmarshalJSON(data []byte) error {
type SecuritySchemeBis SecurityScheme
var x SecuritySchemeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "type")
delete(x.Extensions, "description")
delete(x.Extensions, "name")
delete(x.Extensions, "in")
delete(x.Extensions, "scheme")
delete(x.Extensions, "bearerFormat")
delete(x.Extensions, "flows")
delete(x.Extensions, "openIdConnectUrl")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*ss = SecurityScheme(x)
return nil
}
func (ss *SecurityScheme) WithType(value string) *SecurityScheme {
ss.Type = value
return ss
}
func (ss *SecurityScheme) WithDescription(value string) *SecurityScheme {
ss.Description = value
return ss
}
func (ss *SecurityScheme) WithName(value string) *SecurityScheme {
ss.Name = value
return ss
}
func (ss *SecurityScheme) WithIn(value string) *SecurityScheme {
ss.In = value
return ss
}
func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme {
ss.Scheme = value
return ss
}
func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme {
ss.BearerFormat = value
return ss
}
// Validate returns an error if SecurityScheme does not comply with the OpenAPI spec.
func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
hasIn := false
hasBearerFormat := false
hasFlow := false
switch ss.Type {
case "apiKey":
hasIn = true
case "http":
scheme := ss.Scheme
switch scheme {
case "bearer":
hasBearerFormat = true
case "basic", "negotiate", "digest":
default:
return newInvalidHTTPScheme(scheme, ss.Origin)
}
case "oauth2":
hasFlow = true
case "openIdConnect":
if ss.OpenIdConnectUrl == "" {
return newOpenIDConnectURLRequired(ss.Name, ss.Origin)
}
case "mutualTLS":
if !getValidationOptions(ctx).isOpenAPI31OrLater {
return errValueOfFieldFor31Plus(ss.Type, "type")
}
default:
return newInvalidSecuritySchemeType(ss.Type, ss.Origin)
}
// Validate "in" and "name"
if hasIn {
switch ss.In {
case "query", "header", "cookie":
default:
return newAPIKeyInInvalid(ss.In, ss.Origin)
}
if ss.Name == "" {
return newAPIKeySecuritySchemeNameRequired(ss.Origin)
}
} else if len(ss.In) > 0 {
return newSecuritySchemeInForbidden(ss.Type, ss.Origin)
} else if len(ss.Name) > 0 {
return newSecuritySchemeNameForbidden(ss.Type, ss.Origin)
}
// Validate "format"
// "bearerFormat" is an arbitrary string so we only check if the scheme supports it
if !hasBearerFormat && len(ss.BearerFormat) > 0 {
return newSecuritySchemeBearerFormatForbidden(ss.Type, ss.Origin)
}
// Validate "flow"
if hasFlow {
flow := ss.Flows
if flow == nil {
return newSecuritySchemeFlowsRequired(ss.Type, ss.Origin)
}
if err := flow.Validate(ctx); err != nil {
return &SecuritySchemeFlowValidationError{Cause: err}
}
} else if ss.Flows != nil {
return newSecuritySchemeFlowsForbidden(ss.Type, ss.Origin)
}
return validateExtensions(ctx, ss.Extensions, ss.Origin)
}
// OAuthFlows is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
type OAuthFlows struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"`
}
type oAuthFlowType int
const (
oAuthFlowTypeImplicit oAuthFlowType = iota
oAuthFlowTypePassword
oAuthFlowTypeClientCredentials
oAuthFlowAuthorizationCode
)
// MarshalJSON returns the JSON encoding of OAuthFlows.
func (flows OAuthFlows) MarshalJSON() ([]byte, error) {
x, err := flows.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of OAuthFlows.
func (flows OAuthFlows) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(flows.Extensions))
maps.Copy(m, flows.Extensions)
if x := flows.Implicit; x != nil {
m["implicit"] = x
}
if x := flows.Password; x != nil {
m["password"] = x
}
if x := flows.ClientCredentials; x != nil {
m["clientCredentials"] = x
}
if x := flows.AuthorizationCode; x != nil {
m["authorizationCode"] = x
}
return m, nil
}
// UnmarshalJSON sets OAuthFlows to a copy of data.
func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
type OAuthFlowsBis OAuthFlows
var x OAuthFlowsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "implicit")
delete(x.Extensions, "password")
delete(x.Extensions, "clientCredentials")
delete(x.Extensions, "authorizationCode")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*flows = OAuthFlows(x)
return nil
}
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := flows.Implicit; v != nil {
if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil {
return &OAuthFlowValidationError{FlowKind: "implicit", Cause: err}
}
}
if v := flows.Password; v != nil {
if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil {
return &OAuthFlowValidationError{FlowKind: "password", Cause: err}
}
}
if v := flows.ClientCredentials; v != nil {
if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil {
return &OAuthFlowValidationError{FlowKind: "clientCredentials", Cause: err}
}
}
if v := flows.AuthorizationCode; v != nil {
if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil {
return &OAuthFlowValidationError{FlowKind: "authorizationCode", Cause: err}
}
}
return validateExtensions(ctx, flows.Extensions, flows.Origin)
}
// OAuthFlow is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
type OAuthFlow struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes StringMap[string] `json:"scopes" yaml:"scopes"` // required
}
// MarshalJSON returns the JSON encoding of OAuthFlow.
func (flow OAuthFlow) MarshalJSON() ([]byte, error) {
x, err := flow.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of OAuthFlow.
func (flow OAuthFlow) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(flow.Extensions))
maps.Copy(m, flow.Extensions)
if x := flow.AuthorizationURL; x != "" {
m["authorizationUrl"] = x
}
if x := flow.TokenURL; x != "" {
m["tokenUrl"] = x
}
if x := flow.RefreshURL; x != "" {
m["refreshUrl"] = x
}
m["scopes"] = flow.Scopes
return m, nil
}
// UnmarshalJSON sets OAuthFlow to a copy of data.
func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
type OAuthFlowBis OAuthFlow
var x OAuthFlowBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "authorizationUrl")
delete(x.Extensions, "tokenUrl")
delete(x.Extensions, "refreshUrl")
delete(x.Extensions, "scopes")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*flow = OAuthFlow(x)
return nil
}
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := flow.RefreshURL; v != "" {
if _, err := url.Parse(v); err != nil {
return &OAuthFlowFieldValidationError{Field: "refreshUrl", Cause: err}
}
}
if flow.Scopes == nil {
return newOAuthFlowScopesRequired(flow.Origin)
}
return validateExtensions(ctx, flow.Extensions, flow.Origin)
}
func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
typeIn := func(types ...oAuthFlowType) bool {
return slices.Contains(types, typ)
}
if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true {
switch {
case flow.AuthorizationURL == "" && in:
return newOAuthFlowAuthorizationURLRequired(flow.Origin)
case flow.AuthorizationURL != "" && !in:
return newOAuthFlowAuthorizationURLForbidden(flow.Origin)
case flow.AuthorizationURL != "":
if _, err := url.Parse(flow.AuthorizationURL); err != nil {
return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err)
}
}
}
if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true {
switch {
case flow.TokenURL == "" && in:
return newOAuthFlowTokenURLRequired(flow.Origin)
case flow.TokenURL != "" && !in:
return newOAuthFlowTokenURLForbidden(flow.Origin)
case flow.TokenURL != "":
if _, err := url.Parse(flow.TokenURL); err != nil {
return fmt.Errorf("field 'tokenUrl' is invalid: %w", err)
}
}
}
return flow.Validate(ctx, opts...)
}
// UnmarshalJSON sets SecuritySchemes to a copy of data.
func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error) {
*securitySchemes, err = unmarshalStringMapP[SecuritySchemeRef](data)
return
}

View File

@@ -0,0 +1,17 @@
package openapi3
const (
SerializationSimple = "simple"
SerializationLabel = "label"
SerializationMatrix = "matrix"
SerializationForm = "form"
SerializationSpaceDelimited = "spaceDelimited"
SerializationPipeDelimited = "pipeDelimited"
SerializationDeepObject = "deepObject"
)
// SerializationMethod describes a serialization method of HTTP request's parameters and body.
type SerializationMethod struct {
Style string
Explode bool
}

318
vendor/github.com/getkin/kin-openapi/openapi3/server.go generated vendored Normal file
View File

@@ -0,0 +1,318 @@
package openapi3
import (
"context"
"encoding/json"
"errors"
"maps"
"net/url"
"strings"
)
// Servers is specified by OpenAPI/Swagger standard version 3.
type Servers []*Server
// Validate returns an error if Servers does not comply with the OpenAPI spec.
func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
for _, v := range servers {
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
return me.result()
}
// BasePath returns the base path of the first server in the list, or /.
func (servers Servers) BasePath() (string, error) {
for _, server := range servers {
return server.BasePath()
}
return "/", nil
}
func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) {
rawURL := parsedURL.String()
if i := strings.IndexByte(rawURL, '?'); i >= 0 {
rawURL = rawURL[:i]
}
for _, server := range servers {
pathParams, remaining, ok := server.MatchRawURL(rawURL)
if ok {
return server, pathParams, remaining
}
}
return nil, nil, ""
}
// Server is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
type Server struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
URL string `json:"url" yaml:"url"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables ServerVariables `json:"variables,omitempty" yaml:"variables,omitempty"`
}
// BasePath returns the base path extracted from the default values of variables, if any.
// Assumes a valid struct (per Validate()).
func (server *Server) BasePath() (string, error) {
if server == nil {
return "/", nil
}
uri := server.URL
for _, name := range componentNames(server.Variables) {
uri = strings.ReplaceAll(uri, "{"+name+"}", server.Variables[name].Default)
}
u, err := url.ParseRequestURI(uri)
if err != nil {
return "", err
}
if bp := u.Path; bp != "" {
return bp, nil
}
return "/", nil
}
// MarshalJSON returns the JSON encoding of Server.
func (server Server) MarshalJSON() ([]byte, error) {
x, err := server.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Server.
func (server Server) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(server.Extensions))
maps.Copy(m, server.Extensions)
m["url"] = server.URL
if x := server.Description; x != "" {
m["description"] = x
}
if x := server.Variables; len(x) != 0 {
m["variables"] = x
}
return m, nil
}
// UnmarshalJSON sets Server to a copy of data.
func (server *Server) UnmarshalJSON(data []byte) error {
type ServerBis Server
var x ServerBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "url")
delete(x.Extensions, "description")
delete(x.Extensions, "variables")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
delete(x.Variables, originKey)
*server = Server(x)
return nil
}
func (server Server) ParameterNames() ([]string, error) {
pattern := server.URL
var params []string
for len(pattern) > 0 {
i := strings.IndexByte(pattern, '{')
if i < 0 {
break
}
pattern = pattern[i+1:]
i = strings.IndexByte(pattern, '}')
if i < 0 {
return nil, errors.New("missing '}'")
}
params = append(params, strings.TrimSpace(pattern[:i]))
pattern = pattern[i+1:]
}
return params, nil
}
func (server Server) MatchRawURL(input string) ([]string, string, bool) {
pattern := server.URL
var params []string
for len(pattern) > 0 {
c := pattern[0]
if len(pattern) == 1 && c == '/' {
break
}
if c == '{' {
// Find end of pattern
i := strings.IndexByte(pattern, '}')
if i < 0 {
return nil, "", false
}
pattern = pattern[i+1:]
// Find next matching pattern character or next '/' whichever comes first
np := -1
if len(pattern) > 0 {
np = strings.IndexByte(input, pattern[0])
}
ns := strings.IndexByte(input, '/')
if np < 0 {
i = ns
} else if ns < 0 {
i = np
} else {
i = min(np, ns)
}
if i < 0 {
i = len(input)
}
params = append(params, input[:i])
input = input[i:]
continue
}
if len(input) == 0 || input[0] != c {
return nil, "", false
}
pattern = pattern[1:]
input = input[1:]
}
if input == "" {
input = "/"
}
if input[0] != '/' {
return nil, "", false
}
return params, input, true
}
// Validate returns an error if Server does not comply with the OpenAPI spec.
func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if server.URL == "" {
if err := me.emit(newServerURLRequired(server.Origin)); err != nil {
return err
}
}
opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}")
if opening != closing {
if err := me.emit(newServerURLMismatchedBraces(server.URL, server.Origin)); err != nil {
return err
}
}
if opening != len(server.Variables) {
if err := me.emit(newServerURLUndeclaredVariables(server.URL, server.Origin)); err != nil {
return err
}
}
for _, name := range componentNames(server.Variables) {
v := server.Variables[name]
if !strings.Contains(server.URL, "{"+name+"}") {
if err := me.emit(newServerURLUndeclaredVariables(server.URL, server.Origin)); err != nil {
return err
}
// Variable name doesn't appear in the URL template; descending
// into its Validate would surface findings under a variable
// the URL never references, with no resolution path until the
// URL is fixed.
continue
}
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, server.Extensions, server.Origin))
}
// ServerVariable is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
// ServerVariables is a map of ServerVariable objects keyed by variable name.
type ServerVariables map[string]*ServerVariable
// UnmarshalJSON sets ServerVariables to a copy of data.
func (serverVariables *ServerVariables) UnmarshalJSON(data []byte) (err error) {
*serverVariables, err = unmarshalStringMapP[ServerVariable](data)
return
}
type ServerVariable struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
// MarshalJSON returns the JSON encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
x, err := serverVariable.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(serverVariable.Extensions))
maps.Copy(m, serverVariable.Extensions)
if x := serverVariable.Enum; len(x) != 0 {
m["enum"] = x
}
if x := serverVariable.Default; x != "" {
m["default"] = x
}
if x := serverVariable.Description; x != "" {
m["description"] = x
}
return m, nil
}
// UnmarshalJSON sets ServerVariable to a copy of data.
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
type ServerVariableBis ServerVariable
var x ServerVariableBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "enum")
delete(x.Extensions, "default")
delete(x.Extensions, "description")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*serverVariable = ServerVariable(x)
return nil
}
// Validate returns an error if ServerVariable does not comply with the OpenAPI spec.
func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if serverVariable.Default == "" {
data, err := serverVariable.MarshalJSON()
if err != nil {
return err
}
return newServerVariableDefaultRequired(string(data), serverVariable.Origin)
}
return validateExtensions(ctx, serverVariable.Extensions, serverVariable.Origin)
}

View File

@@ -0,0 +1,64 @@
package openapi3
import "encoding/json"
// StringMap is a map[string]string that ignores the origin in the underlying json representation.
type StringMap[V any] map[string]V
// UnmarshalJSON sets StringMap to a copy of data.
func (stringMap *StringMap[V]) UnmarshalJSON(data []byte) (err error) {
*stringMap, err = unmarshalStringMap[V](data)
return
}
// unmarshalStringMapP unmarshals given json into a map[string]*V
func unmarshalStringMapP[V any](data []byte) (map[string]*V, error) {
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
result := make(map[string]*V, len(m))
for _, k := range componentNames(m) {
value, err := deepCast[V](m[k])
if err != nil {
return nil, err
}
result[k] = value
}
return result, nil
}
// unmarshalStringMap unmarshals given json into a map[string]V
func unmarshalStringMap[V any](data []byte) (map[string]V, error) {
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
result := make(map[string]V, len(m))
for _, k := range componentNames(m) {
value, err := deepCast[V](m[k])
if err != nil {
return nil, err
}
result[k] = *value
}
return result, nil
}
// deepCast casts any value to a value of type V.
func deepCast[V any](value any) (*V, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
var result V
if err = json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}

101
vendor/github.com/getkin/kin-openapi/openapi3/tag.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// Tags is specified by OpenAPI/Swagger 3.0 standard.
type Tags []*Tag
func (tags Tags) Get(name string) *Tag {
for _, tag := range tags {
if tag.Name == name {
return tag
}
}
return nil
}
// Validate returns an error if Tags does not comply with the OpenAPI spec.
func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
for _, v := range tags {
if err := me.emit(v.Validate(ctx)); err != nil {
return err
}
}
return me.result()
}
// Tag is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
type Tag struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
// MarshalJSON returns the JSON encoding of Tag.
func (t Tag) MarshalJSON() ([]byte, error) {
x, err := t.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Tag.
func (t Tag) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(t.Extensions))
maps.Copy(m, t.Extensions)
if x := t.Name; x != "" {
m["name"] = x
}
if x := t.Description; x != "" {
m["description"] = x
}
if x := t.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return m, nil
}
// UnmarshalJSON sets Tag to a copy of data.
func (t *Tag) UnmarshalJSON(data []byte) error {
type TagBis Tag
var x TagBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "description")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*t = Tag(x)
return nil
}
// Validate returns an error if Tag does not comply with the OpenAPI spec.
func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)
if v := t.ExternalDocs; v != nil {
wrap := func(e error) error { return &SectionValidationError{Section: "external docs", Cause: e} }
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
}
}
return me.finalize(validateExtensions(ctx, t.Extensions, t.Origin))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,252 @@
// Context wrappers are the 4th category of typed validation errors,
// alongside the Base / Cluster / Leaf model documented at the top of
// validation_error.go.
//
// A context wrapper carries scope (which section, path, operation,
// component, parameter, OAuth flow, ...) around an inner error chain.
// It does NOT itself report a validation failure — the actual error
// lives in Cause and is reachable via errors.Unwrap / errors.As.
//
// Use a context wrapper to extract "where the error happened" without
// parsing the rendered message. Combine with errors.As against the
// inner cluster or leaf to also extract "what category" and "exactly
// which case." A canonical error chain looks like:
//
// ComponentValidationError{Section, Name} // context wrapper: WHERE
// -> RequiredFieldError{Field} // cluster: WHAT CATEGORY
// -> SomeFieldRequired{Message} // leaf: EXACTLY WHICH CASE
//
// Two scopes of context wrapper live in this file:
//
// - Document-wide wrappers — SectionValidationError,
// PathValidationError, OperationValidationError. Cover entire
// top-level scopes of the document.
// - Narrow-scope wrappers — ComponentValidationError,
// ExternalDocsURLValidationError, HeaderFieldValidationError,
// MediaTypeExampleValidationError, WebhookValidationError,
// ParameterFieldValidationError, ParameterExampleValidationError,
// SecuritySchemeFlowValidationError, OAuthFlowValidationError,
// OAuthFlowFieldValidationError. Cover a specific validation
// surface inside a section.
//
// Both scopes follow the same shape:
//
// - One or more discriminator fields naming the scope (Section,
// Path, ParameterName + Field, etc.).
// - A Cause error that holds the wrapped inner error.
// - Unwrap() returns Cause so errors.As walks transparently to the
// inner cluster or leaf.
//
// Error() formats vary per wrapper: each preserves the original
// fmt.Errorf-with-%w message format byte-for-byte for backward
// compatibility, so existing string-matching consumers see identical
// output. There is no canonical "<context>: <cause>" format across
// wrappers — read each type's Error() if the exact string matters.
package openapi3
import "fmt"
// SectionValidationError wraps an error originating inside one of the
// top-level OpenAPI document sections (info, paths, components,
// security, servers, tags, externalDocs, webhooks, jsonSchemaDialect).
// Section is the OpenAPI field name as it appears in the document
// root.
//
// Use errors.As(err, &sve) to extract the section context from a
// validation error chain without parsing the rendered message.
type SectionValidationError struct {
Section string
Cause error
}
func (e *SectionValidationError) Error() string {
return fmt.Sprintf("invalid %s: %v", e.Section, e.Cause)
}
func (e *SectionValidationError) Unwrap() error { return e.Cause }
// PathValidationError wraps an error originating inside a specific path.
// Path is the path template as it appears in the document (e.g.
// "/users/{id}").
//
// Use errors.As(err, &pve) to extract the path from a validation
// error chain without parsing the rendered message.
type PathValidationError struct {
Path string
Cause error
}
func (e *PathValidationError) Error() string {
return fmt.Sprintf("invalid path %s: %v", e.Path, e.Cause)
}
func (e *PathValidationError) Unwrap() error { return e.Cause }
// OperationValidationError wraps an error originating inside a specific
// HTTP-method operation under a path. Method is the uppercase method
// (GET, POST, etc.).
//
// Use errors.As(err, &ove) to extract the method from a validation
// error chain without parsing the rendered message.
type OperationValidationError struct {
Method string
Cause error
}
func (e *OperationValidationError) Error() string {
return fmt.Sprintf("invalid operation %s: %v", e.Method, e.Cause)
}
func (e *OperationValidationError) Unwrap() error { return e.Cause }
// Below: narrow-scope context wrappers covering specific validation
// surfaces inside a section. See the file-level header above for the
// full inventory and how these relate to the document-wide wrappers
// above.
// ComponentValidationError wraps validation errors inside the
// Components container, carrying which sub-section (Schemas,
// Parameters, etc.) and which component name failed.
type ComponentValidationError struct {
// Section is the lowercase singular form of the component bucket
// ("schema", "parameter", "header", "request body", "response",
// "security scheme", "example", "link", "callback").
Section string
// Name is the component map key.
Name string
Cause error
}
func (e *ComponentValidationError) Error() string {
return fmt.Sprintf("%s %q: %v", e.Section, e.Name, e.Cause)
}
func (e *ComponentValidationError) Unwrap() error { return e.Cause }
// ExternalDocsURLValidationError wraps the URL parse failure on an
// ExternalDocs object.
type ExternalDocsURLValidationError struct {
Cause error
}
func (e *ExternalDocsURLValidationError) Error() string {
return fmt.Sprintf("url is incorrect: %v", e.Cause)
}
func (e *ExternalDocsURLValidationError) Unwrap() error { return e.Cause }
// HeaderFieldValidationError wraps validation errors on a Header's
// `schema` or `content` sub-objects. Field discriminates the two.
type HeaderFieldValidationError struct {
// Field is "schema" or "content".
Field string
Cause error
}
func (e *HeaderFieldValidationError) Error() string {
return fmt.Sprintf("header %s is invalid: %v", e.Field, e.Cause)
}
func (e *HeaderFieldValidationError) Unwrap() error { return e.Cause }
// MediaTypeExampleValidationError wraps validation errors on a named
// example inside a MediaType.examples map.
type MediaTypeExampleValidationError struct {
// ExampleName is the example map key.
ExampleName string
Cause error
}
func (e *MediaTypeExampleValidationError) Error() string {
return fmt.Sprintf("example %s: %v", e.ExampleName, e.Cause)
}
func (e *MediaTypeExampleValidationError) Unwrap() error { return e.Cause }
// WebhookValidationError wraps validation errors on a named webhook
// at the document root (OpenAPI 3.1+).
type WebhookValidationError struct {
// Name is the webhook map key.
Name string
Cause error
}
func (e *WebhookValidationError) Error() string {
return fmt.Sprintf("webhook %q: %v", e.Name, e.Cause)
}
func (e *WebhookValidationError) Unwrap() error { return e.Cause }
// ParameterFieldValidationError wraps validation errors on a
// parameter's `schema` or `content` sub-objects. Field discriminates.
type ParameterFieldValidationError struct {
// ParameterName is the parameter's `name:` value.
ParameterName string
// Field is "schema" or "content".
Field string
Cause error
}
func (e *ParameterFieldValidationError) Error() string {
return fmt.Sprintf("parameter %q %s is invalid: %v", e.ParameterName, e.Field, e.Cause)
}
func (e *ParameterFieldValidationError) Unwrap() error { return e.Cause }
// ParameterExampleValidationError wraps validation errors on a named
// example inside a parameter's examples map.
type ParameterExampleValidationError struct {
// ExampleName is the example map key.
ExampleName string
Cause error
}
func (e *ParameterExampleValidationError) Error() string {
return fmt.Sprintf("%s: %v", e.ExampleName, e.Cause)
}
func (e *ParameterExampleValidationError) Unwrap() error { return e.Cause }
// SecuritySchemeFlowValidationError wraps validation errors on the
// outer flows object of an oauth2 security scheme.
type SecuritySchemeFlowValidationError struct {
Cause error
}
func (e *SecuritySchemeFlowValidationError) Error() string {
return fmt.Sprintf("security scheme 'flow' is invalid: %v", e.Cause)
}
func (e *SecuritySchemeFlowValidationError) Unwrap() error { return e.Cause }
// OAuthFlowValidationError wraps validation errors on a specific
// OAuth flow inside OAuthFlows.
type OAuthFlowValidationError struct {
// FlowKind is one of "implicit", "password", "clientCredentials",
// "authorizationCode".
FlowKind string
Cause error
}
func (e *OAuthFlowValidationError) Error() string {
return fmt.Sprintf("the OAuth flow %q is invalid: %v", e.FlowKind, e.Cause)
}
func (e *OAuthFlowValidationError) Unwrap() error { return e.Cause }
// OAuthFlowFieldValidationError wraps validation errors on a specific
// field inside an OAuthFlow object. Field discriminates which URL
// field failed.
type OAuthFlowFieldValidationError struct {
// Field is the offending field name ("refreshUrl" is the only
// site today; future URL fields can reuse the same wrapper).
Field string
Cause error
}
func (e *OAuthFlowFieldValidationError) Error() string {
return fmt.Sprintf("field %q is invalid: %v", e.Field, e.Cause)
}
func (e *OAuthFlowFieldValidationError) Unwrap() error { return e.Cause }

View File

@@ -0,0 +1,172 @@
package openapi3
import "context"
// ValidationOption allows the modification of how the OpenAPI document is validated.
type ValidationOption func(options *ValidationOptions)
// ValidationOptions provides configuration for validating OpenAPI documents.
type ValidationOptions struct {
examplesValidationAsReq, examplesValidationAsRes bool
examplesValidationDisabled bool
schemaDefaultsValidationDisabled bool
schemaFormatValidationEnabled bool
schemaPatternValidationDisabled bool
schemaExtensionsInRefProhibited bool
jsonSchema2020ValidationEnabled bool
isOpenAPI31OrLater bool
multiErrorEnabled bool
regexCompilerFunc RegexCompilerFunc
extraSiblingFieldsAllowed map[string]struct{}
}
type validationOptionsKey struct{}
// AllowExtraSiblingFields called as AllowExtraSiblingFields("description") makes Validate not return an error when said field appears next to a $ref.
func AllowExtraSiblingFields(fields ...string) ValidationOption {
return func(options *ValidationOptions) {
if options.extraSiblingFieldsAllowed == nil && len(fields) != 0 {
options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields))
}
for _, field := range fields {
options.extraSiblingFieldsAllowed[field] = struct{}{}
}
}
}
// IsOpenAPI31OrLater enables "JSON Schema Draft 2020-12"-compliant validation (for OpenAPI 3.1 documents).
func IsOpenAPI31OrLater() ValidationOption {
return func(options *ValidationOptions) {
options.isOpenAPI31OrLater = true // To distinguish from v3.0
options.jsonSchema2020ValidationEnabled = true // TODO: use even for v3.0
}
}
// EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
// By default, schema format validation is disabled.
func EnableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaFormatValidationEnabled = true
}
}
// DisableSchemaFormatValidation does the opposite of EnableSchemaFormatValidation.
// By default, schema format validation is disabled.
func DisableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaFormatValidationEnabled = false
}
}
// EnableSchemaPatternValidation does the opposite of DisableSchemaPatternValidation.
// By default, schema pattern validation is enabled.
func EnableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaPatternValidationDisabled = false
}
}
// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaPatternValidationDisabled = true
}
}
// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation.
// By default, schema default values are validated against their schema.
func EnableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = false
}
}
// DisableSchemaDefaultsValidation disables schemas' default field validation.
// By default, schema default values are validated against their schema.
func DisableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = true
}
}
// EnableExamplesValidation does the opposite of DisableExamplesValidation.
// By default, all schema examples are validated.
func EnableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.examplesValidationDisabled = false
}
}
// DisableExamplesValidation disables all example schema validation.
// By default, all schema examples are validated.
func DisableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.examplesValidationDisabled = true
}
}
// AllowExtensionsWithRef allows extensions (fields starting with 'x-')
// as siblings for $ref fields. This is the default.
// Non-extension fields are prohibited unless allowed explicitly with the
// AllowExtraSiblingFields option.
func AllowExtensionsWithRef() ValidationOption {
return func(options *ValidationOptions) {
options.schemaExtensionsInRefProhibited = false
}
}
// ProhibitExtensionsWithRef causes the validation to return an
// error if extensions (fields starting with 'x-') are found as
// siblings for $ref fields. Non-extension fields are prohibited
// unless allowed explicitly with the AllowExtraSiblingFields option.
func ProhibitExtensionsWithRef() ValidationOption {
return func(options *ValidationOptions) {
options.schemaExtensionsInRefProhibited = true
}
}
// EnableMultiError makes Validate aggregate independent validation errors and
// return them together as a MultiError instead of returning the first error
// and stopping. By default, Validate is fail-fast.
//
// Not every validator reports more than one error yet. Some, such as Schema,
// run checks that build on earlier ones, so continuing past a failure can hit
// a nil dereference or produce nonsense secondary errors.
// We will keep converting more validators in follow-up changes as each one
// is analyzed.
//
// To pull a specific error type out of the result, use errors.As(err, &target).
// It walks into the MultiError automatically, so the same call works whether
// Validate returned one error or many.
func EnableMultiError() ValidationOption {
return func(options *ValidationOptions) {
options.multiErrorEnabled = true
}
}
// SetRegexCompiler allows to override the regex implementation used to validate
// field "pattern".
func SetRegexCompiler(c RegexCompilerFunc) ValidationOption {
return func(options *ValidationOptions) {
options.regexCompilerFunc = c
}
}
// WithValidationOptions allows adding validation options to a context object that can be used when validating any OpenAPI type.
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context {
if len(opts) == 0 {
return ctx
}
options := &ValidationOptions{}
for _, opt := range opts {
opt(options)
}
return context.WithValue(ctx, validationOptionsKey{}, options)
}
func getValidationOptions(ctx context.Context) *ValidationOptions {
if options, ok := ctx.Value(validationOptionsKey{}).(*ValidationOptions); ok {
return options
}
return &ValidationOptions{}
}

View File

@@ -0,0 +1,41 @@
package openapi3
func newVisited() visitedComponent {
return visitedComponent{
header: make(map[*Header]struct{}),
schema: make(map[*Schema]struct{}),
}
}
type visitedComponent struct {
header map[*Header]struct{}
schema map[*Schema]struct{}
}
// resetVisited clears visitedComponent map
// should be called before recursion over doc *T
func (doc *T) resetVisited() {
doc.visited = newVisited()
}
// isVisitedHeader returns `true` if the *Header pointer was already visited
// otherwise it returns `false`
func (doc *T) isVisitedHeader(h *Header) bool {
if _, ok := doc.visited.header[h]; ok {
return true
}
doc.visited.header[h] = struct{}{}
return false
}
// isVisitedHeader returns `true` if the *Schema pointer was already visited
// otherwise it returns `false`
func (doc *T) isVisitedSchema(s *Schema) bool {
if _, ok := doc.visited.schema[s]; ok {
return true
}
doc.visited.schema[s] = struct{}{}
return false
}

78
vendor/github.com/getkin/kin-openapi/openapi3/xml.go generated vendored Normal file
View File

@@ -0,0 +1,78 @@
package openapi3
import (
"context"
"encoding/json"
"maps"
)
// XML is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object
type XML struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
}
// MarshalJSON returns the JSON encoding of XML.
func (xml XML) MarshalJSON() ([]byte, error) {
x, err := xml.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of XML.
func (xml XML) MarshalYAML() (any, error) {
m := make(map[string]any, 5+len(xml.Extensions))
maps.Copy(m, xml.Extensions)
if x := xml.Name; x != "" {
m["name"] = x
}
if x := xml.Namespace; x != "" {
m["namespace"] = x
}
if x := xml.Prefix; x != "" {
m["prefix"] = x
}
if x := xml.Attribute; x {
m["attribute"] = x
}
if x := xml.Wrapped; x {
m["wrapped"] = x
}
return m, nil
}
// UnmarshalJSON sets XML to a copy of data.
func (xml *XML) UnmarshalJSON(data []byte) error {
type XMLBis XML
var x XMLBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "namespace")
delete(x.Extensions, "prefix")
delete(x.Extensions, "attribute")
delete(x.Extensions, "wrapped")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*xml = XML(x)
return nil
}
// Validate returns an error if XML does not comply with the OpenAPI spec.
func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, xml.Extensions, xml.Origin)
}