Skip to content

Commit de022f1

Browse files
feat: support validation options specifically for disabling pattern validation (#590)
1 parent 5a61040 commit de022f1

File tree

9 files changed

+156
-17
lines changed

9 files changed

+156
-17
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ func arrayUniqueItemsChecker(items []interface{}) bool {
186186

187187
## Sub-v0 breaking API changes
188188

189+
### v0.101.0
190+
* `openapi3.SchemaFormatValidationDisabled` has been removed in favour of an option `openapi3.EnableSchemaFormatValidation()` passed to `openapi3.T.Validate`. The default behaviour is also now to not validate formats, as the OpenAPI spec mentions the `format` is an open value.
191+
189192
### v0.84.0
190193
* The prototype of `openapi3gen.NewSchemaRefForValue` changed:
191194
* It no longer returns a map but that is still accessible under the field `(*Generator).SchemaRefs`.

openapi3/loader.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR
166166

167167
// ResolveRefsIn expands references if for instance spec was just unmarshalled
168168
func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
169+
if loader.Context == nil {
170+
loader.Context = context.Background()
171+
}
172+
169173
if loader.visitedPathItemRefs == nil {
170174
loader.resetVisitedPathItemRefs()
171175
}
@@ -406,7 +410,6 @@ func (loader *Loader) documentPathForRecursiveRef(current *url.URL, resolvedRef
406410
return current
407411
}
408412
return &url.URL{Path: path.Join(loader.rootDir, resolvedRef)}
409-
410413
}
411414

412415
func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) {
@@ -837,7 +840,6 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
837840
}
838841

839842
func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documentPath *url.URL) (err error) {
840-
841843
if component == nil {
842844
return errors.New("invalid callback: value MUST be an object")
843845
}

openapi3/openapi3.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ func (doc *T) AddServer(server *Server) {
5454
}
5555

5656
// Validate returns an error if T does not comply with the OpenAPI spec.
57-
func (doc *T) Validate(ctx context.Context) error {
57+
// Validations Options can be provided to modify the validation behavior.
58+
func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error {
59+
validationOpts := &ValidationOptions{}
60+
for _, opt := range opts {
61+
opt(validationOpts)
62+
}
63+
ctx = WithValidationOptions(ctx, validationOpts)
64+
5865
if doc.OpenAPI == "" {
5966
return errors.New("value of openapi must be a non-empty string")
6067
}

openapi3/schema.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ var (
3636
// SchemaErrorDetailsDisabled disables printing of details about schema errors.
3737
SchemaErrorDetailsDisabled = false
3838

39-
//SchemaFormatValidationDisabled disables validation of schema type formats.
40-
SchemaFormatValidationDisabled = false
41-
4239
errSchema = errors.New("input does not match the schema")
4340

4441
// ErrOneOfConflict is the SchemaError Origin when data matches more than one oneOf schema
@@ -403,6 +400,7 @@ func (schema *Schema) WithMax(value float64) *Schema {
403400
schema.Max = &value
404401
return schema
405402
}
403+
406404
func (schema *Schema) WithExclusiveMin(value bool) *Schema {
407405
schema.ExclusiveMin = value
408406
return schema
@@ -606,6 +604,8 @@ func (schema *Schema) Validate(ctx context.Context) error {
606604
}
607605

608606
func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) {
607+
validationOpts := getValidationOptions(ctx)
608+
609609
for _, existing := range stack {
610610
if existing == schema {
611611
return
@@ -666,7 +666,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
666666
switch format {
667667
case "float", "double":
668668
default:
669-
if !SchemaFormatValidationDisabled {
669+
if validationOpts.SchemaFormatValidationEnabled {
670670
return unsupportedFormat(format)
671671
}
672672
}
@@ -676,7 +676,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
676676
switch format {
677677
case "int32", "int64":
678678
default:
679-
if !SchemaFormatValidationDisabled {
679+
if validationOpts.SchemaFormatValidationEnabled {
680680
return unsupportedFormat(format)
681681
}
682682
}
@@ -698,12 +698,12 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
698698
case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
699699
default:
700700
// Try to check for custom defined formats
701-
if _, ok := SchemaStringFormats[format]; !ok && !SchemaFormatValidationDisabled {
701+
if _, ok := SchemaStringFormats[format]; !ok && validationOpts.SchemaFormatValidationEnabled {
702702
return unsupportedFormat(format)
703703
}
704704
}
705705
}
706-
if schema.Pattern != "" {
706+
if schema.Pattern != "" && !validationOpts.SchemaPatternValidationDisabled {
707707
if err = schema.compilePattern(); err != nil {
708708
return err
709709
}
@@ -1063,7 +1063,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
10631063
formatMin = formatMinInt64
10641064
formatMax = formatMaxInt64
10651065
default:
1066-
if !SchemaFormatValidationDisabled {
1066+
if settings.formatValidationEnabled {
10671067
return unsupportedFormat(schema.Format)
10681068
}
10691069
}
@@ -1237,7 +1237,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
12371237
}
12381238

12391239
// "pattern"
1240-
if schema.Pattern != "" && schema.compiledPattern == nil {
1240+
if schema.Pattern != "" && schema.compiledPattern == nil && !settings.patternValidationDisabled {
12411241
var err error
12421242
if err = schema.compilePattern(); err != nil {
12431243
if !settings.multiError {

openapi3/schema_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,10 @@ func testType(t *testing.T, example schemaTypeExample) func(*testing.T) {
10281028
}
10291029
for _, typ := range example.AllInvalid {
10301030
schema := baseSchema.WithFormat(typ)
1031-
err := schema.Validate(context.Background())
1031+
ctx := WithValidationOptions(context.Background(), &ValidationOptions{
1032+
SchemaFormatValidationEnabled: true,
1033+
})
1034+
err := schema.Validate(ctx)
10321035
require.Error(t, err)
10331036
}
10341037
}
@@ -1308,6 +1311,6 @@ func TestValidationFailsOnInvalidPattern(t *testing.T) {
13081311
Type: "string",
13091312
}
13101313

1311-
var err = schema.Validate(context.Background())
1314+
err := schema.Validate(context.Background())
13121315
require.Error(t, err)
13131316
}

openapi3/schema_validation_settings.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
type SchemaValidationOption func(*schemaValidationSettings)
99

1010
type schemaValidationSettings struct {
11-
failfast bool
12-
multiError bool
13-
asreq, asrep bool // exclusive (XOR) fields
11+
failfast bool
12+
multiError bool
13+
asreq, asrep bool // exclusive (XOR) fields
14+
formatValidationEnabled bool
15+
patternValidationDisabled bool
1416

1517
onceSettingDefaults sync.Once
1618
defaultsSet func()
@@ -28,10 +30,21 @@ func MultiErrors() SchemaValidationOption {
2830
func VisitAsRequest() SchemaValidationOption {
2931
return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false }
3032
}
33+
3134
func VisitAsResponse() SchemaValidationOption {
3235
return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true }
3336
}
3437

38+
// EnableFormatValidation setting makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
39+
func EnableFormatValidation() SchemaValidationOption {
40+
return func(s *schemaValidationSettings) { s.formatValidationEnabled = true }
41+
}
42+
43+
// DisablePatternValidation setting makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
44+
func DisablePatternValidation() SchemaValidationOption {
45+
return func(s *schemaValidationSettings) { s.patternValidationDisabled = true }
46+
}
47+
3548
// DefaultsSet executes the given callback (once) IFF schema validation set default values.
3649
func DefaultsSet(f func()) SchemaValidationOption {
3750
return func(s *schemaValidationSettings) { s.defaultsSet = f }

openapi3/testdata/issue409.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
openapi: 3.0.3
2+
info:
3+
description: Contains Patterns that can't be compiled by the go regexp engine
4+
title: Issue 409
5+
version: 0.0.1
6+
paths:
7+
/v1/apis/{apiID}:
8+
get:
9+
description: Get a list of all Apis and there versions for a given workspace
10+
operationId: getApisV1
11+
parameters:
12+
- description: The ID of the API
13+
in: path
14+
name: apiID
15+
required: true
16+
schema:
17+
type: string
18+
pattern: ^[a-zA-Z0-9]{0,4096}$
19+
responses:
20+
"200":
21+
description: OK
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package openapi3_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/getkin/kin-openapi/openapi3"
10+
)
11+
12+
func TestIssue409PatternIgnored(t *testing.T) {
13+
l := openapi3.NewLoader()
14+
s, err := l.LoadFromFile("testdata/issue409.yml")
15+
require.NoError(t, err)
16+
17+
err = s.Validate(l.Context, openapi3.DisableSchemaPatternValidation())
18+
assert.NoError(t, err)
19+
}
20+
21+
func TestIssue409PatternNotIgnored(t *testing.T) {
22+
l := openapi3.NewLoader()
23+
s, err := l.LoadFromFile("testdata/issue409.yml")
24+
require.NoError(t, err)
25+
26+
err = s.Validate(l.Context)
27+
assert.Error(t, err)
28+
}
29+
30+
func TestIssue409HygienicUseOfCtx(t *testing.T) {
31+
l := openapi3.NewLoader()
32+
doc, err := l.LoadFromFile("testdata/issue409.yml")
33+
require.NoError(t, err)
34+
35+
err = doc.Validate(l.Context, openapi3.DisableSchemaPatternValidation())
36+
assert.NoError(t, err)
37+
err = doc.Validate(l.Context)
38+
assert.Error(t, err)
39+
40+
// and the other way
41+
42+
l = openapi3.NewLoader()
43+
doc, err = l.LoadFromFile("testdata/issue409.yml")
44+
require.NoError(t, err)
45+
46+
err = doc.Validate(l.Context)
47+
assert.Error(t, err)
48+
err = doc.Validate(l.Context, openapi3.DisableSchemaPatternValidation())
49+
assert.NoError(t, err)
50+
}

openapi3/validation_options.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package openapi3
2+
3+
import "context"
4+
5+
// ValidationOption allows the modification of how the OpenAPI document is validated.
6+
type ValidationOption func(options *ValidationOptions)
7+
8+
// ValidationOptions provide configuration for validating OpenAPI documents.
9+
type ValidationOptions struct {
10+
SchemaFormatValidationEnabled bool
11+
SchemaPatternValidationDisabled bool
12+
}
13+
14+
type validationOptionsKey struct{}
15+
16+
// EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
17+
func EnableSchemaFormatValidation() ValidationOption {
18+
return func(options *ValidationOptions) {
19+
options.SchemaFormatValidationEnabled = true
20+
}
21+
}
22+
23+
// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
24+
func DisableSchemaPatternValidation() ValidationOption {
25+
return func(options *ValidationOptions) {
26+
options.SchemaPatternValidationDisabled = true
27+
}
28+
}
29+
30+
// WithValidationOptions allows adding validation options to a context object that can be used when validationg any OpenAPI type.
31+
func WithValidationOptions(ctx context.Context, options *ValidationOptions) context.Context {
32+
return context.WithValue(ctx, validationOptionsKey{}, options)
33+
}
34+
35+
func getValidationOptions(ctx context.Context) *ValidationOptions {
36+
if options, ok := ctx.Value(validationOptionsKey{}).(*ValidationOptions); ok {
37+
return options
38+
}
39+
return &ValidationOptions{}
40+
}

0 commit comments

Comments
 (0)