Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openapi3: unexport ValidationOptions fields and add some more #717

Merged
merged 2 commits into from Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Expand Up @@ -55,6 +55,11 @@ Be sure to check [OpenAPI Initiative](https://github.com/OAI)'s [great tooling l
* Generates `*openapi3.Schema` values for Go types.

# Some recipes
## Validating an OpenAPI document
```shell
go run github.com/getkin/kin-openapi/cmd/validate@latest [--defaults] [--examples] [--ext] [--patterns] -- <local YAML or JSON file>
```

## Loading OpenAPI document
Use `openapi3.Loader`, which resolves all references:
```go
Expand Down Expand Up @@ -196,6 +201,11 @@ func arrayUniqueItemsChecker(items []interface{}) bool {

## Sub-v0 breaking API changes

### v0.112.0
* `(openapi3.ValidationOptions).ExamplesValidationDisabled` has been unexported.
* `(openapi3.ValidationOptions).SchemaFormatValidationEnabled` has been unexported.
* `(openapi3.ValidationOptions).SchemaPatternValidationDisabled` has been unexported.

### v0.111.0
* Changed `func (*_) Validate(ctx context.Context) error` to `func (*_) Validate(ctx context.Context, opts ...ValidationOption) error`.
* `openapi3.WithValidationOptions(ctx context.Context, opts *ValidationOptions) context.Context` prototype changed to `openapi3.WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context`.
Expand Down
102 changes: 102 additions & 0 deletions cmd/validate/main.go
@@ -0,0 +1,102 @@
package main

import (
"flag"
"log"
"os"
"strings"

"github.com/invopop/yaml"

"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
)

var (
defaultDefaults = true
defaults = flag.Bool("defaults", defaultDefaults, "when false, disables schemas' default field validation")
)

var (
defaultExamples = true
examples = flag.Bool("examples", defaultExamples, "when false, disables all example schema validation")
)

var (
defaultExt = false
ext = flag.Bool("ext", defaultExt, "enables visiting other files")
)

var (
defaultPatterns = true
patterns = flag.Bool("patterns", defaultPatterns, "when false, allows schema patterns unsupported by the Go regexp engine")
)

func main() {
flag.Parse()
filename := flag.Arg(0)
if len(flag.Args()) != 1 || filename == "" {
log.Fatalf("Usage: go run github.com/getkin/kin-openapi/cmd/validate@latest [--defaults] [--examples] [--ext] [--patterns] -- <local YAML or JSON file>\nGot: %+v\n", os.Args)
}

data, err := os.ReadFile(filename)
if err != nil {
log.Fatal(err)
}

var vd struct {
OpenAPI string `json:"openapi" yaml:"openapi"`
Swagger string `json:"swagger" yaml:"swagger"`
}
if err := yaml.Unmarshal(data, &vd); err != nil {
log.Fatal(err)
}

switch {
case vd.OpenAPI == "3" || strings.HasPrefix(vd.OpenAPI, "3."):
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = *ext

doc, err := loader.LoadFromFile(filename)
if err != nil {
log.Fatal(err)
}

var opts []openapi3.ValidationOption
if !*defaults {
opts = append(opts, openapi3.DisableSchemaDefaultsValidation())
}
if !*examples {
opts = append(opts, openapi3.DisableExamplesValidation())
}
if !*patterns {
opts = append(opts, openapi3.DisableSchemaPatternValidation())
}

if err = doc.Validate(loader.Context, opts...); err != nil {
log.Fatal(err)
}

case vd.Swagger == "2" || strings.HasPrefix(vd.Swagger, "2."):
if *defaults != defaultDefaults {
log.Fatal("Flag --defaults is only for OpenAPIv3")
}
if *examples != defaultExamples {
log.Fatal("Flag --examples is only for OpenAPIv3")
}
if *ext != defaultExt {
log.Fatal("Flag --ext is only for OpenAPIv3")
}
if *patterns != defaultPatterns {
log.Fatal("Flag --patterns is only for OpenAPIv3")
}

var doc openapi2.T
if err := yaml.Unmarshal(data, &doc); err != nil {
log.Fatal(err)
}

default:
log.Fatal("Missing or incorrect 'openapi' or 'swagger' field")
}
}
2 changes: 1 addition & 1 deletion openapi3/media_type.go
Expand Up @@ -90,7 +90,7 @@ func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOpti
return errors.New("example and examples are mutually exclusive")
}

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

Expand Down
2 changes: 1 addition & 1 deletion openapi3/parameter.go
Expand Up @@ -323,7 +323,7 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti
return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name)
}

if vo := getValidationOptions(ctx); vo.ExamplesValidationDisabled {
if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
return nil
}
if example := parameter.Example; example != nil {
Expand Down
2 changes: 1 addition & 1 deletion openapi3/request_body.go
Expand Up @@ -112,7 +112,7 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation
return errors.New("content of the request body is required")
}

if vo := getValidationOptions(ctx); !vo.ExamplesValidationDisabled {
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
}

Expand Down
2 changes: 1 addition & 1 deletion openapi3/response.go
Expand Up @@ -119,7 +119,7 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption
if response.Description == nil {
return errors.New("a short description of the response is required")
}
if vo := getValidationOptions(ctx); !vo.ExamplesValidationDisabled {
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true
}

Expand Down
12 changes: 6 additions & 6 deletions openapi3/schema.go
Expand Up @@ -678,7 +678,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
switch format {
case "float", "double":
default:
if validationOpts.SchemaFormatValidationEnabled {
if validationOpts.schemaFormatValidationEnabled {
return unsupportedFormat(format)
}
}
Expand All @@ -688,7 +688,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
switch format {
case "int32", "int64":
default:
if validationOpts.SchemaFormatValidationEnabled {
if validationOpts.schemaFormatValidationEnabled {
return unsupportedFormat(format)
}
}
Expand All @@ -710,12 +710,12 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
default:
// Try to check for custom defined formats
if _, ok := SchemaStringFormats[format]; !ok && validationOpts.SchemaFormatValidationEnabled {
if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled {
return unsupportedFormat(format)
}
}
}
if schema.Pattern != "" && !validationOpts.SchemaPatternValidationDisabled {
if schema.Pattern != "" && !validationOpts.schemaPatternValidationDisabled {
if err = schema.compilePattern(); err != nil {
return err
}
Expand Down Expand Up @@ -771,13 +771,13 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
}
}

if v := schema.Default; v != nil {
if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled {
if err := schema.VisitJSON(v); err != nil {
return fmt.Errorf("invalid default: %w", err)
}
}

if x := schema.Example; x != nil && !validationOpts.ExamplesValidationDisabled {
if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled {
if err := validateExampleValue(ctx, x, schema); err != nil {
return fmt.Errorf("invalid example: %w", err)
}
Expand Down
35 changes: 26 additions & 9 deletions openapi3/validation_options.go
Expand Up @@ -7,10 +7,11 @@ type ValidationOption func(options *ValidationOptions)

// ValidationOptions provides configuration for validating OpenAPI documents.
type ValidationOptions struct {
SchemaFormatValidationEnabled bool
SchemaPatternValidationDisabled bool
ExamplesValidationDisabled bool
examplesValidationAsReq, examplesValidationAsRes bool
examplesValidationDisabled bool
schemaDefaultsValidationDisabled bool
schemaFormatValidationEnabled bool
schemaPatternValidationDisabled bool
}

type validationOptionsKey struct{}
Expand All @@ -19,46 +20,62 @@ type validationOptionsKey struct{}
// By default, schema format validation is disabled.
func EnableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.SchemaFormatValidationEnabled = true
options.schemaFormatValidationEnabled = true
}
}

// DisableSchemaFormatValidation does the opposite of EnableSchemaFormatValidation.
// By default, schema format validation is disabled.
func DisableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.SchemaFormatValidationEnabled = false
options.schemaFormatValidationEnabled = false
}
}

// EnableSchemaPatternValidation does the opposite of DisableSchemaPatternValidation.
// By default, schema pattern validation is enabled.
func EnableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.SchemaPatternValidationDisabled = false
options.schemaPatternValidationDisabled = false
}
}

// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.SchemaPatternValidationDisabled = true
options.schemaPatternValidationDisabled = true
}
}

// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation.
// By default, schema default values are validated against their schema.
func EnableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = false
}
}

// DisableSchemaDefaultsValidation disables schemas' default field validation.
// By default, schema default values are validated against their schema.
func DisableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = true
}
}

// EnableExamplesValidation does the opposite of DisableExamplesValidation.
// By default, all schema examples are validated.
func EnableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.ExamplesValidationDisabled = false
options.examplesValidationDisabled = false
}
}

// DisableExamplesValidation disables all example schema validation.
// By default, all schema examples are validated.
func DisableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.ExamplesValidationDisabled = true
options.examplesValidationDisabled = true
}
}

Expand Down