Skip to content

Commit

Permalink
Introduce an option to override the regex implementation (#1006)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbakker authored Aug 26, 2024
1 parent b771380 commit 2a0cad9
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 6 deletions.
14 changes: 14 additions & 0 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,12 @@ type RefNameResolver func(*T, ComponentRef) string
The function should avoid name collisions (i.e. be a injective mapping). It
must only contain characters valid for fixed field names: IdentifierRegExp.

type RegexCompilerFunc func(expr string) (RegexMatcher, error)

type RegexMatcher interface {
MatchString(s string) bool
}

type RequestBodies map[string]*RequestBodyRef

func (m RequestBodies) JSONLookup(token string) (any, error)
Expand Down Expand Up @@ -1764,6 +1770,10 @@ func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaVali
If the passed function returns an empty string, it returns to the previous
Error() implementation.

func SetSchemaRegexCompiler(c RegexCompilerFunc) SchemaValidationOption
SetSchemaRegexCompiler allows to override the regex implementation used to
validate field "pattern".

func VisitAsRequest() SchemaValidationOption

func VisitAsResponse() SchemaValidationOption
Expand Down Expand Up @@ -2128,6 +2138,10 @@ func ProhibitExtensionsWithRef() ValidationOption
fields. Non-extension fields are prohibited unless allowed explicitly with
the AllowExtraSiblingFields option.

func SetRegexCompiler(c RegexCompilerFunc) ValidationOption
SetRegexCompiler allows to override the regex implementation used to
validate field "pattern".

type ValidationOptions struct {
// Has unexported fields.
}
Expand Down
3 changes: 3 additions & 0 deletions .github/docs/openapi3filter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ type Options struct {

MultiError bool

// Set RegexCompiler to override the regex implementation
RegexCompiler openapi3.RegexCompilerFunc

// A document with security schemes defined will not pass validation
// unless an AuthenticationFunc is defined.
// See NoopAuthenticationFunc
Expand Down
7 changes: 3 additions & 4 deletions openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"math"
"math/big"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -1019,7 +1018,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema,
}
}
if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" {
if _, err := schema.compilePattern(); err != nil {
if _, err := schema.compilePattern(validationOpts.regexCompilerFunc); err != nil {
return stack, err
}
}
Expand Down Expand Up @@ -1729,10 +1728,10 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
// "pattern"
if !settings.patternValidationDisabled && schema.Pattern != "" {
cpiface, _ := compiledPatterns.Load(schema.Pattern)
cp, _ := cpiface.(*regexp.Regexp)
cp, _ := cpiface.(RegexMatcher)
if cp == nil {
var err error
if cp, err = schema.compilePattern(); err != nil {
if cp, err = schema.compilePattern(settings.regexCompiler); err != nil {
if !settings.multiError {
return err
}
Expand Down
10 changes: 8 additions & 2 deletions openapi3/schema_pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ func intoGoRegexp(re string) string {
}

// NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns]
func (schema *Schema) compilePattern() (cp *regexp.Regexp, err error) {
func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err error) {
pattern := schema.Pattern
if cp, err = regexp.Compile(intoGoRegexp(pattern)); err != nil {
if c != nil {
cp, err = c(pattern)
} else {
cp, err = regexp.Compile(intoGoRegexp(pattern))
}
if err != nil {
err = &SchemaError{
Schema: schema,
SchemaField: "pattern",
Expand All @@ -24,6 +29,7 @@ func (schema *Schema) compilePattern() (cp *regexp.Regexp, err error) {
}
return
}

var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp)
return
}
13 changes: 13 additions & 0 deletions openapi3/schema_validation_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
// SchemaValidationOption describes options a user has when validating request / response bodies.
type SchemaValidationOption func(*schemaValidationSettings)

type RegexCompilerFunc func(expr string) (RegexMatcher, error)

type RegexMatcher interface {
MatchString(s string) bool
}

type schemaValidationSettings struct {
failfast bool
multiError bool
Expand All @@ -16,6 +22,8 @@ type schemaValidationSettings struct {
readOnlyValidationDisabled bool
writeOnlyValidationDisabled bool

regexCompiler RegexCompilerFunc

onceSettingDefaults sync.Once
defaultsSet func()

Expand Down Expand Up @@ -70,6 +78,11 @@ func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaVali
return func(s *schemaValidationSettings) { s.customizeMessageError = f }
}

// SetSchemaRegexCompiler allows to override the regex implementation used to validate field "pattern".
func SetSchemaRegexCompiler(c RegexCompilerFunc) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.regexCompiler = c }
}

func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
settings := &schemaValidationSettings{}
for _, opt := range opts {
Expand Down
9 changes: 9 additions & 0 deletions openapi3/validation_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type ValidationOptions struct {
schemaFormatValidationEnabled bool
schemaPatternValidationDisabled bool
schemaExtensionsInRefProhibited bool
regexCompilerFunc RegexCompilerFunc
extraSiblingFieldsAllowed map[string]struct{}
}

Expand Down Expand Up @@ -113,6 +114,14 @@ func ProhibitExtensionsWithRef() ValidationOption {
}
}

// SetRegexCompiler allows to override the regex implementation used to validate
// field "pattern".
func SetRegexCompiler(c RegexCompilerFunc) ValidationOption {
return func(options *ValidationOptions) {
options.regexCompilerFunc = c
}
}

// WithValidationOptions allows adding validation options to a context object that can be used when validating any OpenAPI type.
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context {
if len(opts) == 0 {
Expand Down
3 changes: 3 additions & 0 deletions openapi3filter/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type Options struct {

MultiError bool

// Set RegexCompiler to override the regex implementation
RegexCompiler openapi3.RegexCompilerFunc

// A document with security schemes defined will not pass validation
// unless an AuthenticationFunc is defined.
// See NoopAuthenticationFunc
Expand Down
3 changes: 3 additions & 0 deletions openapi3filter/validate_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
if options.ExcludeReadOnlyValidations {
opts = append(opts, openapi3.DisableReadOnlyValidation())
}
if options.RegexCompiler != nil {
opts = append(opts, openapi3.SetSchemaRegexCompiler(options.RegexCompiler))
}

// Validate JSON with the schema
if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
Expand Down

0 comments on commit 2a0cad9

Please sign in to comment.