go.mod: Update oapi-codegen and kin-openapi

This commit is contained in:
sanne 2022-01-11 19:00:14 +01:00 committed by Sanne Raymaekers
parent add17bba45
commit a83cf95d5b
156 changed files with 29663 additions and 2248 deletions

View file

@ -1,13 +1,34 @@
package openapi3
import "context"
import (
"context"
"fmt"
"github.com/go-openapi/jsonpointer"
)
type Callbacks map[string]*CallbackRef
var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
func (c Callbacks) JSONLookup(token string) (interface{}, error) {
ref, ok := c[token]
if ref == nil || !ok {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Callback is specified by OpenAPI/Swagger standard version 3.0.
type Callback map[string]*PathItem
func (value Callback) Validate(c context.Context) error {
func (value Callback) Validate(ctx context.Context) error {
for _, v := range value {
if err := v.Validate(c); err != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}

View file

@ -11,15 +11,15 @@ import (
// Components is specified by OpenAPI/Swagger standard version 3.0.
type Components struct {
ExtensionProps
Schemas map[string]*SchemaRef `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Parameters map[string]*ParameterRef `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Headers map[string]*HeaderRef `json:"headers,omitempty" yaml:"headers,omitempty"`
RequestBodies map[string]*RequestBodyRef `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Responses map[string]*ResponseRef `json:"responses,omitempty" yaml:"responses,omitempty"`
SecuritySchemes map[string]*SecuritySchemeRef `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"`
Links map[string]*LinkRef `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks map[string]*CallbackRef `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
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 Responses `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 {
@ -34,12 +34,12 @@ func (components *Components) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, components)
}
func (components *Components) Validate(c context.Context) (err error) {
func (components *Components) Validate(ctx context.Context) (err error) {
for k, v := range components.Schemas {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(c); err != nil {
if err = v.Validate(ctx); err != nil {
return
}
}
@ -48,7 +48,7 @@ func (components *Components) Validate(c context.Context) (err error) {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(c); err != nil {
if err = v.Validate(ctx); err != nil {
return
}
}
@ -57,7 +57,7 @@ func (components *Components) Validate(c context.Context) (err error) {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(c); err != nil {
if err = v.Validate(ctx); err != nil {
return
}
}
@ -66,7 +66,7 @@ func (components *Components) Validate(c context.Context) (err error) {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(c); err != nil {
if err = v.Validate(ctx); err != nil {
return
}
}
@ -75,7 +75,7 @@ func (components *Components) Validate(c context.Context) (err error) {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(c); err != nil {
if err = v.Validate(ctx); err != nil {
return
}
}
@ -84,7 +84,7 @@ func (components *Components) Validate(c context.Context) (err error) {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(c); err != nil {
if err = v.Validate(ctx); err != nil {
return
}
}
@ -92,13 +92,16 @@ func (components *Components) Validate(c context.Context) (err error) {
return
}
const identifierPattern = `^[a-zA-Z0-9.\-_]+$`
const identifierPattern = `^[a-zA-Z0-9._-]+$`
var identifierRegExp = regexp.MustCompile(identifierPattern)
// IdentifierRegExp verifies whether Component object key matches 'identifierPattern' pattern, according to OapiAPI v3.x.0.
// Hovever, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in orde not to fail
// converted v2-v3 validation
var IdentifierRegExp = regexp.MustCompile(identifierPattern)
func ValidateIdentifier(value string) error {
if identifierRegExp.MatchString(value) {
if IdentifierRegExp.MatchString(value) {
return nil
}
return fmt.Errorf("Identifier '%s' is not supported by OpenAPI version 3 standard (regexp: '%s')", value, identifierPattern)
return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (regexp: %q)", value, identifierPattern)
}

View file

@ -12,6 +12,32 @@ func NewContent() Content {
return make(map[string]*MediaType, 4)
}
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),
@ -23,6 +49,18 @@ func NewContentWithJSONSchemaRef(schema *SchemaRef) Content {
}
}
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
@ -66,10 +104,10 @@ func (content Content) Get(mime string) *MediaType {
return content["*/*"]
}
func (content Content) Validate(c context.Context) error {
for _, v := range content {
func (value Content) Validate(ctx context.Context) error {
for _, v := range value {
// Validate MediaType
if err := v.Validate(c); err != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}

View file

@ -21,6 +21,6 @@ func (value *Discriminator) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *Discriminator) Validate(c context.Context) error {
func (value *Discriminator) Validate(ctx context.Context) error {
return nil
}

View file

@ -1,5 +1,4 @@
// Package openapi3 parses and writes OpenAPI 3 specifications.
// Package openapi3 parses and writes OpenAPI 3 specification documents.
//
// The OpenAPI 3.0 specification can be found at:
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.md
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
package openapi3

View file

@ -11,11 +11,11 @@ import (
type Encoding struct {
ExtensionProps
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers map[string]*HeaderRef `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"`
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 {
@ -61,21 +61,21 @@ func (encoding *Encoding) SerializationMethod() *SerializationMethod {
return sm
}
func (encoding *Encoding) Validate(c context.Context) error {
if encoding == nil {
func (value *Encoding) Validate(ctx context.Context) error {
if value == nil {
return nil
}
for k, v := range encoding.Headers {
for k, v := range value.Headers {
if err := ValidateIdentifier(k); err != nil {
return nil
}
if err := v.Validate(c); err != nil {
if err := v.Validate(ctx); err != nil {
return nil
}
}
// Validate a media types's serialization method.
sm := encoding.SerializationMethod()
sm := value.SerializationMethod()
switch {
case sm.Style == SerializationForm && sm.Explode,
sm.Style == SerializationForm && !sm.Explode,
@ -86,7 +86,7 @@ func (encoding *Encoding) Validate(c context.Context) error {
sm.Style == SerializationDeepObject && sm.Explode:
// it is a valid
default:
return fmt.Errorf("Serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
}
return nil

View file

@ -0,0 +1,43 @@
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 {
buff := &bytes.Buffer{}
for _, e := range me {
buff.WriteString(e.Error())
buff.WriteString(" | ")
}
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 interface{}) bool {
for _, e := range me {
if errors.As(e, target) {
return true
}
}
return false
}

View file

@ -1,9 +1,29 @@
package openapi3
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type Examples map[string]*ExampleRef
var _ jsonpointer.JSONPointable = (*Examples)(nil)
func (e Examples) JSONLookup(token string) (interface{}, error) {
ref, ok := e[token]
if ref == nil || !ok {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Example is specified by OpenAPI/Swagger 3.0 standard.
type Example struct {
ExtensionProps
@ -27,3 +47,7 @@ func (example *Example) MarshalJSON() ([]byte, error) {
func (example *Example) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, example)
}
func (value *Example) Validate(ctx context.Context) error {
return nil // TODO
}

View file

@ -2,32 +2,79 @@ package openapi3
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type Headers map[string]*HeaderRef
var _ jsonpointer.JSONPointable = (*Headers)(nil)
func (h Headers) JSONLookup(token string) (interface{}, error) {
ref, ok := h[token]
if ref == nil || !ok {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
type Header struct {
ExtensionProps
// Optional description. Should use CommonMark syntax.
Description string `json:"description,omitempty" yaml:"description,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 interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
Description string `json:"description,omitempty" yaml:"description,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 interface{} `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 = (*Header)(nil)
func (value *Header) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *Header) Validate(c context.Context) error {
func (value *Header) Validate(ctx context.Context) error {
if v := value.Schema; v != nil {
if err := v.Validate(c); err != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
func (value Header) JSONLookup(token string) (interface{}, error) {
switch token {
case "schema":
if value.Schema != nil {
if value.Schema.Ref != "" {
return &Ref{Ref: value.Schema.Ref}, nil
}
return value.Schema.Value, nil
}
case "description":
return value.Description, nil
case "deprecated":
return value.Deprecated, nil
case "required":
return value.Required, nil
case "example":
return value.Example, nil
case "examples":
return value.Examples, nil
case "content":
return value.Content, nil
}
v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
return v, err
}

View file

@ -26,25 +26,25 @@ func (value *Info) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *Info) Validate(c context.Context) error {
func (value *Info) Validate(ctx context.Context) error {
if contact := value.Contact; contact != nil {
if err := contact.Validate(c); err != nil {
if err := contact.Validate(ctx); err != nil {
return err
}
}
if license := value.License; license != nil {
if err := license.Validate(c); err != nil {
if err := license.Validate(ctx); err != nil {
return err
}
}
if value.Version == "" {
return errors.New("value of version must be a non-empty JSON string")
return errors.New("value of version must be a non-empty string")
}
if value.Title == "" {
return errors.New("value of title must be a non-empty JSON string")
return errors.New("value of title must be a non-empty string")
}
return nil
@ -66,7 +66,7 @@ func (value *Contact) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *Contact) Validate(c context.Context) error {
func (value *Contact) Validate(ctx context.Context) error {
return nil
}
@ -85,9 +85,9 @@ func (value *License) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *License) Validate(c context.Context) error {
func (value *License) Validate(ctx context.Context) error {
if value.Name == "" {
return errors.New("value of license name must be a non-empty JSON string")
return errors.New("value of license name must be a non-empty string")
}
return nil
}

View file

@ -6,8 +6,25 @@ import (
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type Links map[string]*LinkRef
func (l Links) JSONLookup(token string) (interface{}, error) {
ref, ok := l[token]
if ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
var _ jsonpointer.JSONPointable = (*Links)(nil)
// Link is specified by OpenAPI/Swagger standard version 3.0.
type Link struct {
ExtensionProps
@ -27,12 +44,12 @@ func (value *Link) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *Link) Validate(c context.Context) error {
func (value *Link) Validate(ctx context.Context) error {
if value.OperationID == "" && value.OperationRef == "" {
return errors.New("missing operationId or operationRef on link")
}
if value.OperationID != "" && value.OperationRef != "" {
return fmt.Errorf("operationId '%s' and operationRef '%s' are mutually exclusive", value.OperationID, value.OperationRef)
return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", value.OperationID, value.OperationRef)
}
return nil
}

1012
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

@ -4,18 +4,21 @@ import (
"context"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
type MediaType struct {
ExtensionProps
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
}
var _ jsonpointer.JSONPointable = (*MediaType)(nil)
func NewMediaType() *MediaType {
return &MediaType{}
}
@ -24,9 +27,7 @@ func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType {
if schema == nil {
mediaType.Schema = nil
} else {
mediaType.Schema = &SchemaRef{
Value: schema,
}
mediaType.Schema = &SchemaRef{Value: schema}
}
return mediaType
}
@ -66,14 +67,34 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, mediaType)
}
func (mediaType *MediaType) Validate(c context.Context) error {
if mediaType == nil {
func (value *MediaType) Validate(ctx context.Context) error {
if value == nil {
return nil
}
if schema := mediaType.Schema; schema != nil {
if err := schema.Validate(c); err != nil {
if schema := value.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return err
}
}
return nil
}
func (mediaType MediaType) JSONLookup(token string) (interface{}, 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.ExtensionProps, token)
return v, err
}

View file

@ -8,7 +8,8 @@ import (
"github.com/getkin/kin-openapi/jsoninfo"
)
type Swagger struct {
// T is the root of an OpenAPI v3 document
type T struct {
ExtensionProps
OpenAPI string `json:"openapi" yaml:"openapi"` // Required
Components Components `json:"components,omitempty" yaml:"components,omitempty"`
@ -20,19 +21,19 @@ type Swagger struct {
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
func (swagger *Swagger) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(swagger)
func (doc *T) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(doc)
}
func (swagger *Swagger) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, swagger)
func (doc *T) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, doc)
}
func (swagger *Swagger) AddOperation(path string, method string, operation *Operation) {
paths := swagger.Paths
func (doc *T) AddOperation(path string, method string, operation *Operation) {
paths := doc.Paths
if paths == nil {
paths = make(Paths)
swagger.Paths = paths
doc.Paths = paths
}
pathItem := paths[path]
if pathItem == nil {
@ -42,50 +43,50 @@ func (swagger *Swagger) AddOperation(path string, method string, operation *Oper
pathItem.SetOperation(method, operation)
}
func (swagger *Swagger) AddServer(server *Server) {
swagger.Servers = append(swagger.Servers, server)
func (doc *T) AddServer(server *Server) {
doc.Servers = append(doc.Servers, server)
}
func (swagger *Swagger) Validate(c context.Context) error {
if swagger.OpenAPI == "" {
return errors.New("value of openapi must be a non-empty JSON string")
func (value *T) Validate(ctx context.Context) error {
if value.OpenAPI == "" {
return errors.New("value of openapi must be a non-empty string")
}
// NOTE: only mention info/components/paths/... key in this func's errors.
{
wrap := func(e error) error { return fmt.Errorf("invalid components: %v", e) }
if err := swagger.Components.Validate(c); err != nil {
if err := value.Components.Validate(ctx); err != nil {
return wrap(err)
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid info: %v", e) }
if v := swagger.Info; v != nil {
if err := v.Validate(c); err != nil {
if v := value.Info; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
} else {
return wrap(errors.New("must be a JSON object"))
return wrap(errors.New("must be an object"))
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid paths: %v", e) }
if v := swagger.Paths; v != nil {
if err := v.Validate(c); err != nil {
if v := value.Paths; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
} else {
return wrap(errors.New("must be a JSON object"))
return wrap(errors.New("must be an object"))
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid security: %v", e) }
if v := swagger.Security; v != nil {
if err := v.Validate(c); err != nil {
if v := value.Security; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
@ -93,8 +94,8 @@ func (swagger *Swagger) Validate(c context.Context) error {
{
wrap := func(e error) error { return fmt.Errorf("invalid servers: %v", e) }
if v := swagger.Servers; v != nil {
if err := v.Validate(c); err != nil {
if v := value.Servers; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}

View file

@ -6,6 +6,7 @@ import (
"strconv"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
@ -34,7 +35,7 @@ type Operation struct {
Responses Responses `json:"responses" yaml:"responses"` // Required
// Optional callbacks
Callbacks map[string]*CallbackRef `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
@ -47,6 +48,8 @@ type Operation struct {
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
var _ jsonpointer.JSONPointable = (*Operation)(nil)
func NewOperation() *Operation {
return &Operation{}
}
@ -59,6 +62,43 @@ func (operation *Operation) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, operation)
}
func (operation Operation) JSONLookup(token string) (interface{}, 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.ExtensionProps, token)
return v, err
}
func (operation *Operation) AddParameter(p *Parameter) {
operation.Parameters = append(operation.Parameters, &ParameterRef{
Value: p,
@ -80,23 +120,23 @@ func (operation *Operation) AddResponse(status int, response *Response) {
}
}
func (operation *Operation) Validate(c context.Context) error {
if v := operation.Parameters; v != nil {
if err := v.Validate(c); err != nil {
func (value *Operation) Validate(ctx context.Context) error {
if v := value.Parameters; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
if v := operation.RequestBody; v != nil {
if err := v.Validate(c); err != nil {
if v := value.RequestBody; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
if v := operation.Responses; v != nil {
if err := v.Validate(c); err != nil {
if v := value.Responses; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
} else {
return errors.New("value of responses must be a JSON object")
return errors.New("value of responses must be an object")
}
return nil
}

View file

@ -4,13 +4,51 @@ import (
"context"
"errors"
"fmt"
"strconv"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type ParametersMap map[string]*ParameterRef
var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
func (p ParametersMap) JSONLookup(token string) (interface{}, error) {
ref, ok := p[token]
if ref == nil || ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Parameters is specified by OpenAPI/Swagger 3.0 standard.
type Parameters []*ParameterRef
var _ jsonpointer.JSONPointable = (*Parameters)(nil)
func (p Parameters) JSONLookup(token string) (interface{}, 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)
}
@ -26,9 +64,9 @@ func (parameters Parameters) GetByInAndName(in string, name string) *Parameter {
return nil
}
func (parameters Parameters) Validate(c context.Context) error {
func (value Parameters) Validate(ctx context.Context) error {
dupes := make(map[string]struct{})
for _, item := range parameters {
for _, item := range value {
if v := item.Value; v != nil {
key := v.In + ":" + v.Name
if _, ok := dupes[key]; ok {
@ -37,7 +75,7 @@ func (parameters Parameters) Validate(c context.Context) error {
dupes[key] = struct{}{}
}
if err := item.Validate(c); err != nil {
if err := item.Validate(ctx); err != nil {
return err
}
}
@ -47,21 +85,23 @@ func (parameters Parameters) Validate(c context.Context) error {
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
type Parameter struct {
ExtensionProps
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 interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
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 interface{} `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"
@ -127,6 +167,45 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, parameter)
}
func (parameter Parameter) JSONLookup(token string) (interface{}, 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.ExtensionProps, 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.
@ -157,11 +236,11 @@ func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error)
}
}
func (parameter *Parameter) Validate(c context.Context) error {
if parameter.Name == "" {
func (value *Parameter) Validate(ctx context.Context) error {
if value.Name == "" {
return errors.New("parameter name can't be blank")
}
in := parameter.In
in := value.In
switch in {
case
ParameterInPath,
@ -169,55 +248,55 @@ func (parameter *Parameter) Validate(c context.Context) error {
ParameterInHeader,
ParameterInCookie:
default:
return fmt.Errorf("parameter can't have 'in' value %q", parameter.In)
return fmt.Errorf("parameter can't have 'in' value %q", value.In)
}
// Validate a parameter's serialization method.
sm, err := parameter.SerializationMethod()
sm, err := value.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,
case value.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
value.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,
value.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
value.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
value.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
value.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
value.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
smSupported = true
}
if !smSupported {
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in)
return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e)
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
}
if (parameter.Schema == nil) == (parameter.Content == nil) {
if (value.Schema == nil) == (value.Content == nil) {
e := errors.New("parameter must contain exactly one of content and schema")
return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e)
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
}
if schema := parameter.Schema; schema != nil {
if err := schema.Validate(c); err != nil {
return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, err)
if schema := value.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err)
}
}
if content := parameter.Content; content != nil {
if err := content.Validate(c); err != nil {
return fmt.Errorf("parameter %q content is invalid: %v", parameter.Name, err)
if content := value.Content; content != nil {
if err := content.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q content is invalid: %v", value.Name, err)
}
}
return nil

View file

@ -87,7 +87,7 @@ func (pathItem *PathItem) GetOperation(method string) *Operation {
case http.MethodTrace:
return pathItem.Trace
default:
panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
@ -112,13 +112,13 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
case http.MethodTrace:
pathItem.Trace = operation
default:
panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
func (pathItem *PathItem) Validate(c context.Context) error {
for _, operation := range pathItem.Operations() {
if err := operation.Validate(c); err != nil {
func (value *PathItem) Validate(ctx context.Context) error {
for _, operation := range value.Operations() {
if err := operation.Validate(ctx); err != nil {
return err
}
}

View file

@ -9,13 +9,18 @@ import (
// Paths is specified by OpenAPI/Swagger standard version 3.0.
type Paths map[string]*PathItem
func (paths Paths) Validate(c context.Context) error {
func (value Paths) Validate(ctx context.Context) error {
normalizedPaths := make(map[string]string)
for path, pathItem := range paths {
for path, pathItem := range value {
if path == "" || path[0] != '/' {
return fmt.Errorf("path %q does not start with a forward slash (/)", path)
}
if pathItem == nil {
value[path] = &PathItem{}
pathItem = value[path]
}
normalizedPath, pathParamsCount := normalizeTemplatedPath(path)
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
return fmt.Errorf("conflicting paths %q and %q", path, oldPath)
@ -44,7 +49,7 @@ func (paths Paths) Validate(c context.Context) error {
}
}
if err := pathItem.Validate(c); err != nil {
if err := pathItem.Validate(ctx); err != nil {
return err
}
}

View file

@ -4,13 +4,21 @@ import (
"context"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// Ref is specified by OpenAPI/Swagger 3.0 standard.
type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
}
type CallbackRef struct {
Ref string
Value *Callback
}
var _ jsonpointer.JSONPointable = (*CallbackRef)(nil)
func (value *CallbackRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -19,12 +27,20 @@ func (value *CallbackRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *CallbackRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *CallbackRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value CallbackRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type ExampleRef struct {
@ -32,6 +48,8 @@ type ExampleRef struct {
Value *Example
}
var _ jsonpointer.JSONPointable = (*ExampleRef)(nil)
func (value *ExampleRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -40,8 +58,20 @@ func (value *ExampleRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *ExampleRef) Validate(c context.Context) error {
return nil
func (value *ExampleRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(value.Ref)
}
func (value ExampleRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type HeaderRef struct {
@ -49,6 +79,8 @@ type HeaderRef struct {
Value *Header
}
var _ jsonpointer.JSONPointable = (*HeaderRef)(nil)
func (value *HeaderRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -57,12 +89,20 @@ func (value *HeaderRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *HeaderRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *HeaderRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value HeaderRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type LinkRef struct {
@ -78,12 +118,11 @@ func (value *LinkRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *LinkRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *LinkRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
type ParameterRef struct {
@ -91,6 +130,8 @@ type ParameterRef struct {
Value *Parameter
}
var _ jsonpointer.JSONPointable = (*ParameterRef)(nil)
func (value *ParameterRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -99,12 +140,20 @@ func (value *ParameterRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *ParameterRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *ParameterRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value ParameterRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type ResponseRef struct {
@ -112,6 +161,8 @@ type ResponseRef struct {
Value *Response
}
var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
func (value *ResponseRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -120,12 +171,20 @@ func (value *ResponseRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *ResponseRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *ResponseRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value ResponseRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type RequestBodyRef struct {
@ -133,6 +192,8 @@ type RequestBodyRef struct {
Value *RequestBody
}
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
func (value *RequestBodyRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -141,12 +202,20 @@ func (value *RequestBodyRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *RequestBodyRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *RequestBodyRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type SchemaRef struct {
@ -154,6 +223,8 @@ type SchemaRef struct {
Value *Schema
}
var _ jsonpointer.JSONPointable = (*SchemaRef)(nil)
func NewSchemaRef(ref string, value *Schema) *SchemaRef {
return &SchemaRef{
Ref: ref,
@ -169,12 +240,20 @@ func (value *SchemaRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *SchemaRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *SchemaRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value SchemaRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}
type SecuritySchemeRef struct {
@ -182,6 +261,8 @@ type SecuritySchemeRef struct {
Value *SecurityScheme
}
var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil)
func (value *SecuritySchemeRef) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalRef(value.Ref, value.Value)
}
@ -190,10 +271,18 @@ func (value *SecuritySchemeRef) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
}
func (value *SecuritySchemeRef) Validate(c context.Context) error {
v := value.Value
if v == nil {
return foundUnresolvedRef(value.Ref)
func (value *SecuritySchemeRef) Validate(ctx context.Context) error {
if v := value.Value; v != nil {
return v.Validate(ctx)
}
return v.Validate(c)
return foundUnresolvedRef(value.Ref)
}
func (value SecuritySchemeRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return value.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(value.Value, token)
return ptr, err
}

View file

@ -2,10 +2,28 @@ package openapi3
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type RequestBodies map[string]*RequestBodyRef
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
ref, ok := r[token]
if ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
type RequestBody struct {
ExtensionProps
@ -33,6 +51,16 @@ func (requestBody *RequestBody) WithContent(content Content) *RequestBody {
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
@ -43,6 +71,16 @@ func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody {
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 {
@ -59,9 +97,9 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, requestBody)
}
func (requestBody *RequestBody) Validate(c context.Context) error {
if v := requestBody.Content; v != nil {
if err := v.Validate(c); err != nil {
func (value *RequestBody) Validate(ctx context.Context) error {
if v := value.Content; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}

View file

@ -3,14 +3,18 @@ package openapi3
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// Responses is specified by OpenAPI/Swagger 3.0 standard.
type Responses map[string]*ResponseRef
var _ jsonpointer.JSONPointable = (*Responses)(nil)
func NewResponses() Responses {
r := make(Responses)
r["default"] = &ResponseRef{Value: NewResponse().WithDescription("")}
@ -25,25 +29,37 @@ func (responses Responses) Get(status int) *ResponseRef {
return responses[strconv.FormatInt(int64(status), 10)]
}
func (responses Responses) Validate(c context.Context) error {
if len(responses) == 0 {
func (value Responses) Validate(ctx context.Context) error {
if len(value) == 0 {
return errors.New("the responses object MUST contain at least one response code")
}
for _, v := range responses {
if err := v.Validate(c); err != nil {
for _, v := range value {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
func (responses Responses) JSONLookup(token string) (interface{}, error) {
ref, ok := responses[token]
if ok == false {
return nil, fmt.Errorf("invalid token reference: %q", token)
}
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Response is specified by OpenAPI/Swagger 3.0 standard.
type Response struct {
ExtensionProps
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
Headers map[string]*HeaderRef `json:"headers,omitempty" yaml:"headers,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
Links map[string]*LinkRef `json:"links,omitempty" yaml:"links,omitempty"`
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 {
@ -78,13 +94,13 @@ func (response *Response) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, response)
}
func (response *Response) Validate(c context.Context) error {
if response.Description == nil {
func (value *Response) Validate(ctx context.Context) error {
if value.Description == nil {
return errors.New("a short description of the response is required")
}
if content := response.Content; content != nil {
if err := content.Validate(c); err != nil {
if content := value.Content; content != nil {
if err := content.Validate(ctx); err != nil {
return err
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@ package openapi3
import (
"fmt"
"net"
"regexp"
)
@ -10,15 +11,70 @@ const (
FormatOfStringForUUIDOfRFC4122 = `^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`
)
var SchemaStringFormats = make(map[string]*regexp.Regexp, 8)
//FormatCallback custom check on exotic formats
type FormatCallback func(Val string) error
type Format struct {
regexp *regexp.Regexp
callback FormatCallback
}
//SchemaStringFormats allows for validating strings format
var SchemaStringFormats = make(map[string]Format, 8)
//DefineStringFormat Defines a new regexp pattern for a given format
func DefineStringFormat(name string, pattern string) {
re, err := regexp.Compile(pattern)
if err != nil {
err := fmt.Errorf("Format '%v' has invalid pattern '%v': %v", name, pattern, err)
err := fmt.Errorf("format %q has invalid pattern %q: %v", name, pattern, err)
panic(err)
}
SchemaStringFormats[name] = re
SchemaStringFormats[name] = Format{regexp: re}
}
// DefineStringFormatCallback adds a validation function for a specific schema format entry
func DefineStringFormatCallback(name string, callback FormatCallback) {
SchemaStringFormats[name] = Format{callback: callback}
}
func validateIP(ip string) (*net.IP, error) {
parsed := net.ParseIP(ip)
if parsed == nil {
return nil, &SchemaError{
Value: ip,
Reason: "Not an IP address",
}
}
return &parsed, nil
}
func validateIPv4(ip string) error {
parsed, err := validateIP(ip)
if err != nil {
return err
}
if parsed.To4() == nil {
return &SchemaError{
Value: ip,
Reason: "Not an IPv4 address (it's IPv6)",
}
}
return nil
}
func validateIPv6(ip string) error {
parsed, err := validateIP(ip)
if err != nil {
return err
}
if parsed.To4() != nil {
return &SchemaError{
Value: ip,
Reason: "Not an IPv6 address (it's IPv4)",
}
}
return nil
}
func init() {
@ -35,4 +91,15 @@ func init() {
// date-time
DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`)
}
// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
func DefineIPv4Format() {
DefineStringFormatCallback("ipv4", validateIPv4)
}
// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec
func DefineIPv6Format() {
DefineStringFormatCallback("ipv6", validateIPv6)
}

View file

@ -0,0 +1,34 @@
package openapi3
// SchemaValidationOption describes options a user has when validating request / response bodies.
type SchemaValidationOption func(*schemaValidationSettings)
type schemaValidationSettings struct {
failfast bool
multiError bool
asreq, asrep bool // exclusive (XOR) fields
}
// 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 }
}
func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
settings := &schemaValidationSettings{}
for _, opt := range opts {
opt(settings)
}
return settings
}

View file

@ -15,9 +15,9 @@ func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *
return srs
}
func (srs SecurityRequirements) Validate(c context.Context) error {
for _, item := range srs {
if err := item.Validate(c); err != nil {
func (value SecurityRequirements) Validate(ctx context.Context) error {
for _, item := range value {
if err := item.Validate(ctx); err != nil {
return err
}
}
@ -38,6 +38,6 @@ func (security SecurityRequirement) Authenticate(provider string, scopes ...stri
return security
}
func (security SecurityRequirement) Validate(c context.Context) error {
func (value SecurityRequirement) Validate(ctx context.Context) error {
return nil
}

View file

@ -6,18 +6,36 @@ import (
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type SecuritySchemes map[string]*SecuritySchemeRef
func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) {
ref, ok := s[token]
if ref == nil || ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
type SecurityScheme struct {
ExtensionProps
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"`
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 {
@ -32,6 +50,13 @@ func NewCSRFSecurityScheme() *SecurityScheme {
}
}
func NewOIDCSecurityScheme(oidcUrl string) *SecurityScheme {
return &SecurityScheme{
Type: "openIdConnect",
OpenIdConnectUrl: oidcUrl,
}
}
func NewJWTSecurityScheme() *SecurityScheme {
return &SecurityScheme{
Type: "http",
@ -78,63 +103,65 @@ func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme {
return ss
}
func (ss *SecurityScheme) Validate(c context.Context) error {
func (value *SecurityScheme) Validate(ctx context.Context) error {
hasIn := false
hasBearerFormat := false
hasFlow := false
switch ss.Type {
switch value.Type {
case "apiKey":
hasIn = true
case "http":
scheme := ss.Scheme
scheme := value.Scheme
switch scheme {
case "bearer":
hasBearerFormat = true
case "basic":
case "basic", "negotiate", "digest":
default:
return fmt.Errorf("Security scheme of type 'http' has invalid 'scheme' value '%s'", scheme)
return fmt.Errorf("security scheme of type 'http' has invalid 'scheme' value %q", scheme)
}
case "oauth2":
hasFlow = true
case "openIdConnect":
return fmt.Errorf("Support for security schemes with type '%v' has not been implemented", ss.Type)
if value.OpenIdConnectUrl == "" {
return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", value.Name)
}
default:
return fmt.Errorf("Security scheme 'type' can't be '%v'", ss.Type)
return fmt.Errorf("security scheme 'type' can't be %q", value.Type)
}
// Validate "in" and "name"
if hasIn {
switch ss.In {
switch value.In {
case "query", "header", "cookie":
default:
return fmt.Errorf("Security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not '%s'", ss.In)
return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", value.In)
}
if ss.Name == "" {
return errors.New("Security scheme of type 'apiKey' should have 'name'")
if value.Name == "" {
return errors.New("security scheme of type 'apiKey' should have 'name'")
}
} else if len(ss.In) > 0 {
return fmt.Errorf("Security scheme of type '%s' can't have 'in'", ss.Type)
} else if len(ss.Name) > 0 {
return errors.New("Security scheme of type 'apiKey' can't have 'name'")
} else if len(value.In) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'in'", value.Type)
} else if len(value.Name) > 0 {
return errors.New("security scheme of type 'apiKey' can't have 'name'")
}
// Validate "format"
// "bearerFormat" is an arbitrary string so we only check if the scheme supports it
if !hasBearerFormat && len(ss.BearerFormat) > 0 {
return fmt.Errorf("Security scheme of type '%v' can't have 'bearerFormat'", ss.Type)
if !hasBearerFormat && len(value.BearerFormat) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", value.Type)
}
// Validate "flow"
if hasFlow {
flow := ss.Flows
flow := value.Flows
if flow == nil {
return fmt.Errorf("Security scheme of type '%v' should have 'flows'", ss.Type)
return fmt.Errorf("security scheme of type %q should have 'flows'", value.Type)
}
if err := flow.Validate(c); err != nil {
return fmt.Errorf("Security scheme 'flow' is invalid: %v", err)
if err := flow.Validate(ctx); err != nil {
return fmt.Errorf("security scheme 'flow' is invalid: %v", err)
}
} else if ss.Flows != nil {
return fmt.Errorf("Security scheme of type '%s' can't have 'flows'", ss.Type)
} else if value.Flows != nil {
return fmt.Errorf("security scheme of type %q can't have 'flows'", value.Type)
}
return nil
}
@ -164,20 +191,20 @@ func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, flows)
}
func (flows *OAuthFlows) Validate(c context.Context) error {
func (flows *OAuthFlows) Validate(ctx context.Context) error {
if v := flows.Implicit; v != nil {
return v.Validate(c, oAuthFlowTypeImplicit)
return v.Validate(ctx, oAuthFlowTypeImplicit)
}
if v := flows.Password; v != nil {
return v.Validate(c, oAuthFlowTypePassword)
return v.Validate(ctx, oAuthFlowTypePassword)
}
if v := flows.ClientCredentials; v != nil {
return v.Validate(c, oAuthFlowTypeClientCredentials)
return v.Validate(ctx, oAuthFlowTypeClientCredentials)
}
if v := flows.AuthorizationCode; v != nil {
return v.Validate(c, oAuthFlowAuthorizationCode)
return v.Validate(ctx, oAuthFlowAuthorizationCode)
}
return errors.New("No OAuth flow is defined")
return errors.New("no OAuth flow is defined")
}
type OAuthFlow struct {
@ -196,19 +223,19 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, flow)
}
func (flow *OAuthFlow) Validate(c context.Context, typ oAuthFlowType) error {
func (flow *OAuthFlow) Validate(ctx context.Context, typ oAuthFlowType) error {
if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit {
if v := flow.AuthorizationURL; v == "" {
return errors.New("An OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '")
return errors.New("an OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '")
}
}
if typ != oAuthFlowTypeImplicit {
if v := flow.TokenURL; v == "" {
return errors.New("An OAuth flow is missing 'tokenUrl in not implicit'")
return errors.New("an OAuth flow is missing 'tokenUrl in not implicit'")
}
}
if v := flow.Scopes; v == nil {
return errors.New("An OAuth flow is missing 'scopes'")
return errors.New("an OAuth flow is missing 'scopes'")
}
return nil
}

View file

@ -3,17 +3,21 @@ package openapi3
import (
"context"
"errors"
"fmt"
"math"
"net/url"
"strings"
"github.com/getkin/kin-openapi/jsoninfo"
)
// Servers is specified by OpenAPI/Swagger standard version 3.0.
type Servers []*Server
func (servers Servers) Validate(c context.Context) error {
for _, v := range servers {
if err := v.Validate(c); err != nil {
// Validate ensures servers are per the OpenAPIv3 specification.
func (value Servers) Validate(ctx context.Context) error {
for _, v := range value {
if err := v.Validate(ctx); err != nil {
return err
}
}
@ -36,11 +40,20 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
// Server is specified by OpenAPI/Swagger standard version 3.0.
type Server struct {
ExtensionProps
URL string `json:"url" yaml:"url"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
}
func (server *Server) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(server)
}
func (server *Server) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, server)
}
func (server Server) ParameterNames() ([]string, error) {
pattern := server.URL
var params []string
@ -52,7 +65,7 @@ func (server Server) ParameterNames() ([]string, error) {
pattern = pattern[i+1:]
i = strings.IndexByte(pattern, '}')
if i < 0 {
return nil, errors.New("Missing '}'")
return nil, errors.New("missing '}'")
}
params = append(params, strings.TrimSpace(pattern[:i]))
pattern = pattern[i+1:]
@ -112,12 +125,22 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) {
return params, input, true
}
func (server *Server) Validate(c context.Context) (err error) {
if server.URL == "" {
return errors.New("value of url must be a non-empty JSON string")
func (value *Server) Validate(ctx context.Context) (err error) {
if value.URL == "" {
return errors.New("value of url must be a non-empty string")
}
for _, v := range server.Variables {
if err = v.Validate(c); err != nil {
opening, closing := strings.Count(value.URL, "{"), strings.Count(value.URL, "}")
if opening != closing {
return errors.New("server URL has mismatched { and }")
}
if opening != len(value.Variables) {
return errors.New("server has undeclared variables")
}
for name, v := range value.Variables {
if !strings.Contains(value.URL, fmt.Sprintf("{%s}", name)) {
return errors.New("server has undeclared variables")
}
if err = v.Validate(ctx); err != nil {
return
}
}
@ -126,23 +149,27 @@ func (server *Server) Validate(c context.Context) (err error) {
// ServerVariable is specified by OpenAPI/Swagger standard version 3.0.
type ServerVariable struct {
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExtensionProps
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
func (serverVariable *ServerVariable) Validate(c context.Context) error {
switch serverVariable.Default.(type) {
case float64, string:
default:
return errors.New("value of default must be either JSON number or JSON string")
}
for _, item := range serverVariable.Enum {
switch item.(type) {
case float64, string:
default:
return errors.New("Every variable 'enum' item must be number of string")
func (serverVariable *ServerVariable) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(serverVariable)
}
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, serverVariable)
}
func (value *ServerVariable) Validate(ctx context.Context) error {
if value.Default == "" {
data, err := value.MarshalJSON()
if err != nil {
return err
}
return fmt.Errorf("field default is required in %s", data)
}
return nil
}

View file

@ -1,887 +0,0 @@
package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"reflect"
"strconv"
"strings"
"github.com/ghodss/yaml"
)
func foundUnresolvedRef(ref string) error {
return fmt.Errorf("Found unresolved ref: '%s'", ref)
}
func failedToResolveRefFragment(value string) error {
return fmt.Errorf("Failed to resolve fragment in URI: '%s'", value)
}
func failedToResolveRefFragmentPart(value string, what string) error {
return fmt.Errorf("Failed to resolve '%s' in fragment in URI: '%s'", what, value)
}
type SwaggerLoader struct {
IsExternalRefsAllowed bool
Context context.Context
LoadSwaggerFromURIFunc func(loader *SwaggerLoader, url *url.URL) (*Swagger, error)
visited map[interface{}]struct{}
visitedFiles map[string]struct{}
}
func NewSwaggerLoader() *SwaggerLoader {
return &SwaggerLoader{}
}
func (swaggerLoader *SwaggerLoader) reset() {
swaggerLoader.visitedFiles = make(map[string]struct{})
}
func (swaggerLoader *SwaggerLoader) LoadSwaggerFromURI(location *url.URL) (*Swagger, error) {
swaggerLoader.reset()
return swaggerLoader.loadSwaggerFromURIInternal(location)
}
func (swaggerLoader *SwaggerLoader) loadSwaggerFromURIInternal(location *url.URL) (*Swagger, error) {
f := swaggerLoader.LoadSwaggerFromURIFunc
if f != nil {
return f(swaggerLoader, location)
}
data, err := readURL(location)
if err != nil {
return nil, err
}
return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, location)
}
// loadSingleElementFromURI read the data from ref and unmarshal to JSON to the
// passed element.
func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) error {
if !swaggerLoader.IsExternalRefsAllowed {
return fmt.Errorf("encountered non-allowed external reference: '%s'", ref)
}
parsedURL, err := url.Parse(ref)
if err != nil {
return err
}
if parsedURL.Fragment != "" {
return errors.New("references to files which contain more than one element definition are not supported")
}
resolvedPath, err := resolvePath(rootPath, parsedURL)
if err != nil {
return fmt.Errorf("could not resolve path: %v", err)
}
data, err := readURL(resolvedPath)
if err != nil {
return err
}
if err := yaml.Unmarshal(data, element); err != nil {
return err
}
return nil
}
func readURL(location *url.URL) ([]byte, error) {
if location.Scheme != "" && location.Host != "" {
resp, err := http.Get(location.String())
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, err
}
return data, nil
}
if location.Scheme != "" || location.Host != "" || location.RawQuery != "" {
return nil, fmt.Errorf("Unsupported URI: '%s'", location.String())
}
data, err := ioutil.ReadFile(location.Path)
if err != nil {
return nil, err
}
return data, nil
}
func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger, error) {
swaggerLoader.reset()
return swaggerLoader.loadSwaggerFromFileInternal(path)
}
func (swaggerLoader *SwaggerLoader) loadSwaggerFromFileInternal(path string) (*Swagger, error) {
f := swaggerLoader.LoadSwaggerFromURIFunc
if f != nil {
return f(swaggerLoader, &url.URL{
Path: path,
})
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, &url.URL{
Path: path,
})
}
func (swaggerLoader *SwaggerLoader) LoadSwaggerFromData(data []byte) (*Swagger, error) {
swaggerLoader.reset()
return swaggerLoader.loadSwaggerFromDataInternal(data)
}
func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataInternal(data []byte) (*Swagger, error) {
swagger := &Swagger{}
if err := yaml.Unmarshal(data, swagger); err != nil {
return nil, err
}
return swagger, swaggerLoader.ResolveRefsIn(swagger, nil)
}
// LoadSwaggerFromDataWithPath takes the OpenApi spec data in bytes and a path where the resolver can find referred
// elements and returns a *Swagger with all resolved data or an error if unable to load data or resolve refs.
func (swaggerLoader *SwaggerLoader) LoadSwaggerFromDataWithPath(data []byte, path *url.URL) (*Swagger, error) {
swaggerLoader.reset()
return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, path)
}
func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataWithPathInternal(data []byte, path *url.URL) (*Swagger, error) {
swagger := &Swagger{}
if err := yaml.Unmarshal(data, swagger); err != nil {
return nil, err
}
return swagger, swaggerLoader.ResolveRefsIn(swagger, path)
}
func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.URL) (err error) {
swaggerLoader.visited = make(map[interface{}]struct{})
if swaggerLoader.visitedFiles == nil {
swaggerLoader.visitedFiles = make(map[string]struct{})
}
// Visit all components
components := swagger.Components
for _, component := range components.Headers {
if err = swaggerLoader.resolveHeaderRef(swagger, component, path); err != nil {
return
}
}
for _, component := range components.Parameters {
if err = swaggerLoader.resolveParameterRef(swagger, component, path); err != nil {
return
}
}
for _, component := range components.RequestBodies {
if err = swaggerLoader.resolveRequestBodyRef(swagger, component, path); err != nil {
return
}
}
for _, component := range components.Responses {
if err = swaggerLoader.resolveResponseRef(swagger, component, path); err != nil {
return
}
}
for _, component := range components.Schemas {
if err = swaggerLoader.resolveSchemaRef(swagger, component, path); err != nil {
return
}
}
for _, component := range components.SecuritySchemes {
if err = swaggerLoader.resolveSecuritySchemeRef(swagger, component, path); err != nil {
return
}
}
for _, component := range components.Examples {
if err = swaggerLoader.resolveExampleRef(swagger, component, path); err != nil {
return
}
}
// Visit all operations
for entrypoint, pathItem := range swagger.Paths {
if pathItem == nil {
continue
}
if err = swaggerLoader.resolvePathItemRef(swagger, entrypoint, pathItem, path); err != nil {
return
}
}
return
}
func copyURL(basePath *url.URL) (*url.URL, error) {
return url.Parse(basePath.String())
}
func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) {
if basePath == nil {
return relativePath, nil
}
newPath, err := copyURL(basePath)
if err != nil {
return nil, fmt.Errorf("Can't copy path: '%s'", basePath.String())
}
newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path)
return newPath, nil
}
func resolvePath(basePath *url.URL, componentPath *url.URL) (*url.URL, error) {
if componentPath.Scheme == "" && componentPath.Host == "" {
return join(basePath, componentPath)
}
return componentPath, nil
}
func isSingleRefElement(ref string) bool {
return !strings.Contains(ref, "#")
}
func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref string, path *url.URL) (
cursor interface{},
componentPath *url.URL,
err error,
) {
if swagger, ref, componentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, path); err != nil {
return nil, nil, err
}
parsedURL, err := url.Parse(ref)
if err != nil {
return nil, nil, fmt.Errorf("Can't parse reference: '%s': %v", ref, parsedURL)
}
fragment := parsedURL.Fragment
if !strings.HasPrefix(fragment, "/") {
err := fmt.Errorf("expected fragment prefix '#/' in URI '%s'", ref)
return nil, nil, err
}
cursor = swagger
for _, pathPart := range strings.Split(fragment[1:], "/") {
pathPart = strings.Replace(pathPart, "~1", "/", -1)
pathPart = strings.Replace(pathPart, "~0", "~", -1)
if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil {
return nil, nil, fmt.Errorf("Failed to resolve '%s' in fragment in URI: '%s': %v", ref, pathPart, err.Error())
}
if cursor == nil {
return nil, nil, failedToResolveRefFragmentPart(ref, pathPart)
}
}
return cursor, componentPath, nil
}
func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, error) {
switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() {
case reflect.Map:
elementValue := val.MapIndex(reflect.ValueOf(fieldName))
if !elementValue.IsValid() {
return nil, fmt.Errorf("Map key not found: %v", fieldName)
}
return elementValue.Interface(), nil
case reflect.Slice:
i, err := strconv.ParseUint(fieldName, 10, 32)
if err != nil {
return nil, err
}
index := int(i)
if index >= val.Len() {
return nil, errors.New("slice index out of bounds")
}
return val.Index(index).Interface(), nil
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)
tagValue := field.Tag.Get("yaml")
yamlKey := strings.Split(tagValue, ",")[0]
if yamlKey == fieldName {
return val.Field(i).Interface(), nil
}
}
// if cursor if a "ref wrapper" struct (e.g. RequestBodyRef), try digging into its Value field
_, ok := val.Type().FieldByName("Value")
if ok {
return drillIntoSwaggerField(val.FieldByName("Value").Interface(), fieldName) // recurse into .Value
}
// give up
return nil, fmt.Errorf("Struct field not found: %v", fieldName)
default:
return nil, errors.New("not a map, slice nor struct")
}
}
func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref string, path *url.URL) (*Swagger, string, *url.URL, error) {
componentPath := path
if !strings.HasPrefix(ref, "#") {
if !swaggerLoader.IsExternalRefsAllowed {
return nil, "", nil, fmt.Errorf("Encountered non-allowed external reference: '%s'", ref)
}
parsedURL, err := url.Parse(ref)
if err != nil {
return nil, "", nil, fmt.Errorf("Can't parse reference: '%s': %v", ref, parsedURL)
}
fragment := parsedURL.Fragment
parsedURL.Fragment = ""
resolvedPath, err := resolvePath(path, parsedURL)
if err != nil {
return nil, "", nil, fmt.Errorf("Error while resolving path: %v", err)
}
if swagger, err = swaggerLoader.loadSwaggerFromURIInternal(resolvedPath); err != nil {
return nil, "", nil, fmt.Errorf("Error while resolving reference '%s': %v", ref, err)
}
ref = fmt.Sprintf("#%s", fragment)
componentPath = resolvedPath
}
return swagger, ref, componentPath, nil
}
func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, path *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/headers/"
if component == nil {
return errors.New("invalid header: value MUST be a JSON object")
}
if ref := component.Ref; len(ref) > 0 {
if isSingleRefElement(ref) {
var header Header
if err := swaggerLoader.loadSingleElementFromURI(ref, path, &header); err != nil {
return err
}
component.Value = &header
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*HeaderRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveHeaderRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
if schema := value.Schema; schema != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, schema, path); err != nil {
return err
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/parameters/"
if component == nil {
return errors.New("invalid parameter: value MUST be a JSON object")
}
ref := component.Ref
if len(ref) > 0 {
if isSingleRefElement(ref) {
var param Parameter
if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &param); err != nil {
return err
}
component.Value = &param
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*ParameterRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveParameterRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
refDocumentPath, err := referencedDocumentPath(documentPath, ref)
if err != nil {
return err
}
if value.Content != nil && value.Schema != nil {
return errors.New("Cannot contain both schema and content in a parameter")
}
for _, contentType := range value.Content {
if schema := contentType.Schema; schema != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil {
return err
}
}
}
if schema := value.Schema; schema != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil {
return err
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, path *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/requestBodies/"
if component == nil {
return errors.New("invalid requestBody: value MUST be a JSON object")
}
if ref := component.Ref; len(ref) > 0 {
if isSingleRefElement(ref) {
var requestBody RequestBody
if err := swaggerLoader.loadSingleElementFromURI(ref, path, &requestBody); err != nil {
return err
}
component.Value = &requestBody
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*RequestBodyRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err = swaggerLoader.resolveRequestBodyRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
for _, contentType := range value.Content {
for name, example := range contentType.Examples {
if err := swaggerLoader.resolveExampleRef(swagger, example, path); err != nil {
return err
}
contentType.Examples[name] = example
}
if schema := contentType.Schema; schema != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, schema, path); err != nil {
return err
}
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/responses/"
if component == nil {
return errors.New("invalid response: value MUST be a JSON object")
}
ref := component.Ref
if len(ref) > 0 {
if isSingleRefElement(ref) {
var resp Response
if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil {
return err
}
component.Value = &resp
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*ResponseRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveResponseRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
refDocumentPath, err := referencedDocumentPath(documentPath, ref)
if err != nil {
return err
}
value := component.Value
if value == nil {
return nil
}
for _, header := range value.Headers {
if err := swaggerLoader.resolveHeaderRef(swagger, header, refDocumentPath); err != nil {
return err
}
}
for _, contentType := range value.Content {
if contentType == nil {
continue
}
for name, example := range contentType.Examples {
if err := swaggerLoader.resolveExampleRef(swagger, example, refDocumentPath); err != nil {
return err
}
contentType.Examples[name] = example
}
if schema := contentType.Schema; schema != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil {
return err
}
contentType.Schema = schema
}
}
for _, link := range value.Links {
if err := swaggerLoader.resolveLinkRef(swagger, link, refDocumentPath); err != nil {
return err
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/schemas/"
if component == nil {
return errors.New("invalid schema: value MUST be a JSON object")
}
ref := component.Ref
if len(ref) > 0 {
if isSingleRefElement(ref) {
var schema Schema
if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil {
return err
}
component.Value = &schema
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*SchemaRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveSchemaRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
refDocumentPath, err := referencedDocumentPath(documentPath, ref)
if err != nil {
return err
}
value := component.Value
if value == nil {
return nil
}
// ResolveRefs referred schemas
if v := value.Items; v != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
for _, v := range value.Properties {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
if v := value.AdditionalProperties; v != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
if v := value.Not; v != nil {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
for _, v := range value.AllOf {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
for _, v := range value.AnyOf {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
for _, v := range value.OneOf {
if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
return err
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, path *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/securitySchemes/"
if component == nil {
return errors.New("invalid securityScheme: value MUST be a JSON object")
}
if ref := component.Ref; len(ref) > 0 {
if isSingleRefElement(ref) {
var scheme SecurityScheme
if err := swaggerLoader.loadSingleElementFromURI(ref, path, &scheme); err != nil {
return err
}
component.Value = &scheme
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*SecuritySchemeRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveSecuritySchemeRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, path *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/examples/"
if component == nil {
return errors.New("invalid example: value MUST be a JSON object")
}
if ref := component.Ref; len(ref) > 0 {
if isSingleRefElement(ref) {
var example Example
if err := swaggerLoader.loadSingleElementFromURI(ref, path, &example); err != nil {
return err
}
component.Value = &example
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*ExampleRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveExampleRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, path *url.URL) error {
visited := swaggerLoader.visited
if _, isVisited := visited[component]; isVisited {
return nil
}
visited[component] = struct{}{}
const prefix = "#/components/links/"
if component == nil {
return errors.New("invalid link: value MUST be a JSON object")
}
if ref := component.Ref; len(ref) > 0 {
if isSingleRefElement(ref) {
var link Link
if err := swaggerLoader.loadSingleElementFromURI(ref, path, &link); err != nil {
return err
}
component.Value = &link
} else {
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
if err != nil {
return err
}
resolved, ok := untypedResolved.(*LinkRef)
if !ok {
return failedToResolveRefFragment(ref)
}
if err := swaggerLoader.resolveLinkRef(swagger, resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
return nil
}
func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) {
visited := swaggerLoader.visitedFiles
key := "_"
if documentPath != nil {
key = documentPath.EscapedPath()
}
key += entrypoint
if _, isVisited := visited[key]; isVisited {
return nil
}
visited[key] = struct{}{}
const prefix = "#/paths/"
if pathItem == nil {
return errors.New("invalid path item: value MUST be a JSON object")
}
ref := pathItem.Ref
if ref != "" {
if isSingleRefElement(ref) {
var p PathItem
if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
return err
}
*pathItem = p
} else {
if swagger, ref, documentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, documentPath); err != nil {
return
}
if !strings.HasPrefix(ref, prefix) {
err = fmt.Errorf("expected prefix '%s' in URI '%s'", prefix, ref)
return
}
id := unescapeRefString(ref[len(prefix):])
definitions := swagger.Paths
if definitions == nil {
return failedToResolveRefFragmentPart(ref, "paths")
}
resolved := definitions[id]
if resolved == nil {
return failedToResolveRefFragmentPart(ref, id)
}
*pathItem = *resolved
}
}
refDocumentPath, err := referencedDocumentPath(documentPath, ref)
if err != nil {
return err
}
for _, parameter := range pathItem.Parameters {
if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil {
return
}
}
for _, operation := range pathItem.Operations() {
for _, parameter := range operation.Parameters {
if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil {
return
}
}
if requestBody := operation.RequestBody; requestBody != nil {
if err = swaggerLoader.resolveRequestBodyRef(swagger, requestBody, refDocumentPath); err != nil {
return
}
}
for _, response := range operation.Responses {
if err = swaggerLoader.resolveResponseRef(swagger, response, refDocumentPath); err != nil {
return
}
}
}
return nil
}
func unescapeRefString(ref string) string {
return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1)
}
func referencedDocumentPath(documentPath *url.URL, ref string) (*url.URL, error) {
newDocumentPath := documentPath
if documentPath != nil {
refDirectory, err := url.Parse(path.Dir(ref))
if err != nil {
return nil, err
}
joinedDirectory := path.Join(path.Dir(documentPath.String()), refDirectory.String())
if newDocumentPath, err = url.Parse(joinedDirectory + "/"); err != nil {
return nil, err
}
}
return newDocumentPath, nil
}

View file

@ -1,5 +1,7 @@
package openapi3
import "github.com/getkin/kin-openapi/jsoninfo"
// Tags is specified by OpenAPI/Swagger 3.0 standard.
type Tags []*Tag
@ -14,7 +16,16 @@ func (tags Tags) Get(name string) *Tag {
// Tag is specified by OpenAPI/Swagger 3.0 standard.
type Tag struct {
ExtensionProps
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
func (t *Tag) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(t)
}
func (t *Tag) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, t)
}