Skip to content

Introduce an option to override the regex implementation #1006

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

Merged
merged 1 commit into from
Aug 26, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading