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

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)
}