Skip to content

Commit

Permalink
Deterministic validation (#602)
Browse files Browse the repository at this point in the history
  • Loading branch information
fenollp committed Sep 22, 2022
1 parent b304f8c commit 62f85cf
Show file tree
Hide file tree
Showing 17 changed files with 1,284 additions and 81 deletions.
9 changes: 8 additions & 1 deletion openapi3/callback.go
Expand Up @@ -3,6 +3,7 @@ package openapi3
import (
"context"
"fmt"
"sort"

"github.com/go-openapi/jsonpointer"
)
Expand Down Expand Up @@ -30,7 +31,13 @@ type Callback map[string]*PathItem

// Validate returns an error if Callback does not comply with the OpenAPI spec.
func (callback Callback) Validate(ctx context.Context) error {
for _, v := range callback {
keys := make([]string, 0, len(callback))
for key := range callback {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
v := callback[key]
if err := v.Validate(ctx); err != nil {
return err
}
Expand Down
73 changes: 64 additions & 9 deletions openapi3/components.go
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"regexp"
"sort"

"github.com/getkin/kin-openapi/jsoninfo"
)
Expand Down Expand Up @@ -40,7 +41,13 @@ func (components *Components) UnmarshalJSON(data []byte) error {

// Validate returns an error if Components does not comply with the OpenAPI spec.
func (components *Components) Validate(ctx context.Context) (err error) {
for k, v := range components.Schemas {
schemas := make([]string, 0, len(components.Schemas))
for name := range components.Schemas {
schemas = append(schemas, name)
}
sort.Strings(schemas)
for _, k := range schemas {
v := components.Schemas[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -49,7 +56,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.Parameters {
parameters := make([]string, 0, len(components.Parameters))
for name := range components.Parameters {
parameters = append(parameters, name)
}
sort.Strings(parameters)
for _, k := range parameters {
v := components.Parameters[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -58,7 +71,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.RequestBodies {
requestBodies := make([]string, 0, len(components.RequestBodies))
for name := range components.RequestBodies {
requestBodies = append(requestBodies, name)
}
sort.Strings(requestBodies)
for _, k := range requestBodies {
v := components.RequestBodies[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -67,7 +86,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.Responses {
responses := make([]string, 0, len(components.Responses))
for name := range components.Responses {
responses = append(responses, name)
}
sort.Strings(responses)
for _, k := range responses {
v := components.Responses[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -76,7 +101,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.Headers {
headers := make([]string, 0, len(components.Headers))
for name := range components.Headers {
headers = append(headers, name)
}
sort.Strings(headers)
for _, k := range headers {
v := components.Headers[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -85,7 +116,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.SecuritySchemes {
securitySchemes := make([]string, 0, len(components.SecuritySchemes))
for name := range components.SecuritySchemes {
securitySchemes = append(securitySchemes, name)
}
sort.Strings(securitySchemes)
for _, k := range securitySchemes {
v := components.SecuritySchemes[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -94,7 +131,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.Examples {
examples := make([]string, 0, len(components.Examples))
for name := range components.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, k := range examples {
v := components.Examples[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -103,7 +146,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.Links {
links := make([]string, 0, len(components.Links))
for name := range components.Links {
links = append(links, name)
}
sort.Strings(links)
for _, k := range links {
v := components.Links[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand All @@ -112,7 +161,13 @@ func (components *Components) Validate(ctx context.Context) (err error) {
}
}

for k, v := range components.Callbacks {
callbacks := make([]string, 0, len(components.Callbacks))
for name := range components.Callbacks {
callbacks = append(callbacks, name)
}
sort.Strings(callbacks)
for _, k := range callbacks {
v := components.Callbacks[k]
if err = ValidateIdentifier(k); err != nil {
return
}
Expand Down
9 changes: 8 additions & 1 deletion openapi3/content.go
Expand Up @@ -2,6 +2,7 @@ package openapi3

import (
"context"
"sort"
"strings"
)

Expand Down Expand Up @@ -106,7 +107,13 @@ func (content Content) Get(mime string) *MediaType {

// Validate returns an error if Content does not comply with the OpenAPI spec.
func (content Content) Validate(ctx context.Context) error {
for _, v := range content {
keys := make([]string, 0, len(content))
for key := range content {
keys = append(keys, key)
}
sort.Strings(keys)
for _, k := range keys {
v := content[k]
if err := v.Validate(ctx); err != nil {
return err
}
Expand Down
11 changes: 9 additions & 2 deletions openapi3/encoding.go
Expand Up @@ -3,6 +3,7 @@ package openapi3
import (
"context"
"fmt"
"sort"

"github.com/getkin/kin-openapi/jsoninfo"
)
Expand Down Expand Up @@ -69,7 +70,14 @@ func (encoding *Encoding) Validate(ctx context.Context) error {
if encoding == nil {
return nil
}
for k, v := range encoding.Headers {

headers := make([]string, 0, len(encoding.Headers))
for k := range encoding.Headers {
headers = append(headers, k)
}
sort.Strings(headers)
for _, k := range headers {
v := encoding.Headers[k]
if err := ValidateIdentifier(k); err != nil {
return nil
}
Expand All @@ -88,7 +96,6 @@ func (encoding *Encoding) Validate(ctx context.Context) error {
sm.Style == SerializationPipeDelimited && sm.Explode,
sm.Style == SerializationPipeDelimited && !sm.Explode,
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)
}
Expand Down
34 changes: 34 additions & 0 deletions openapi3/issue601_test.go
@@ -0,0 +1,34 @@
package openapi3

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestIssue601(t *testing.T) {
// Document is invalid: first validation error returned is because
// schema:
// example: {key: value}
// is not how schema examples are defined (but how components' examples are defined. Components are maps.)
// Correct code should be:
// schema: {example: value}
sl := NewLoader()
doc, err := sl.LoadFromFile("testdata/lxkns.yaml")
require.NoError(t, err)

err = doc.Validate(sl.Context)
require.Contains(t, err.Error(), `invalid components: invalid schema example: Error at "/type": property "type" is missing`)
require.Contains(t, err.Error(), `| Error at "/nsid": property "nsid" is missing`)

err = doc.Validate(sl.Context, DisableExamplesValidation())
require.NoError(t, err)

// Now let's remove all the invalid parts
for _, schema := range doc.Components.Schemas {
schema.Value.Example = nil
}

err = doc.Validate(sl.Context)
require.NoError(t, err)
}
27 changes: 24 additions & 3 deletions openapi3/loader.go
Expand Up @@ -9,6 +9,7 @@ import (
"path"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -208,11 +209,19 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
return
}
}
for _, component := range components.Examples {

examples := make([]string, 0, len(components.Examples))
for name := range components.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, name := range examples {
component := components.Examples[name]
if err = loader.resolveExampleRef(doc, component, location); err != nil {
return
}
}

for _, component := range components.Callbacks {
if err = loader.resolveCallbackRef(doc, component, location); err != nil {
return
Expand Down Expand Up @@ -587,7 +596,13 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
}

for _, contentType := range value.Content {
for name, example := range contentType.Examples {
examples := make([]string, 0, len(contentType.Examples))
for name := range contentType.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, name := range examples {
example := contentType.Examples[name]
if err := loader.resolveExampleRef(doc, example, documentPath); err != nil {
return err
}
Expand Down Expand Up @@ -651,7 +666,13 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
if contentType == nil {
continue
}
for name, example := range contentType.Examples {
examples := make([]string, 0, len(contentType.Examples))
for name := range contentType.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, name := range examples {
example := contentType.Examples[name]
if err := loader.resolveExampleRef(doc, example, documentPath); err != nil {
return err
}
Expand Down
16 changes: 14 additions & 2 deletions openapi3/media_type.go
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sort"

"github.com/go-openapi/jsonpointer"

Expand Down Expand Up @@ -82,18 +83,29 @@ func (mediaType *MediaType) Validate(ctx context.Context) error {
if err := schema.Validate(ctx); err != nil {
return err
}

if mediaType.Example != nil && mediaType.Examples != nil {
return errors.New("example and examples are mutually exclusive")
}

if validationOpts := getValidationOptions(ctx); validationOpts.ExamplesValidationDisabled {
return nil
}

if example := mediaType.Example; example != nil {
if err := validateExampleValue(example, schema.Value); err != nil {
return err
}
} else if examples := mediaType.Examples; examples != nil {
for k, v := range examples {
}

if examples := mediaType.Examples; examples != nil {
names := make([]string, 0, len(examples))
for name := range examples {
names = append(names, name)
}
sort.Strings(names)
for _, k := range names {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("%s: %w", k, err)
}
Expand Down

0 comments on commit 62f85cf

Please sign in to comment.