Skip to content

Commit

Permalink
openapi3: unexport ValidationOptions fields and add some more (#717)
Browse files Browse the repository at this point in the history
  • Loading branch information
fenollp committed Dec 19, 2022
1 parent 3be535f commit de2455e
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 19 deletions.
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

0 comments on commit de2455e

Please sign in to comment.