diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6233f67e8..04c631983 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -65,6 +65,9 @@ jobs: run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]] - run: go test ./... + - run: go test -v -run TestRaceyPatternSchema -race ./... + env: + CGO_ENABLED: '1' - run: | cd openapi3/testdata go get -u -v github.com/getkin/kin-openapi diff --git a/openapi3/race_test.go b/openapi3/race_test.go new file mode 100644 index 000000000..4ac31c38e --- /dev/null +++ b/openapi3/race_test.go @@ -0,0 +1,27 @@ +package openapi3_test + +import ( + "context" + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/require" +) + +func TestRaceyPatternSchema(t *testing.T) { + schema := openapi3.Schema{ + Pattern: "^test|for|race|condition$", + Type: "string", + } + + err := schema.Validate(context.Background()) + require.NoError(t, err) + + visit := func() { + err := schema.VisitJSONString("test") + require.NoError(t, err) + } + + go visit() + visit() +} diff --git a/openapi3/schema.go b/openapi3/schema.go index 8c59f3c04..832ce3b3f 100644 --- a/openapi3/schema.go +++ b/openapi3/schema.go @@ -677,6 +677,11 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) } } } + if schema.Pattern != "" { + if err = schema.compilePattern(); err != nil { + return err + } + } case "array": if schema.Items == nil { return errors.New("when schema type is 'array', schema 'items' must be non-null") @@ -1139,15 +1144,9 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value } // "pattern" - if pattern := schema.Pattern; pattern != "" && schema.compiledPattern == nil { + if schema.Pattern != "" && schema.compiledPattern == nil { var err error - if schema.compiledPattern, err = regexp.Compile(pattern); err != nil { - err = &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "pattern", - Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), - } + if err = schema.compilePattern(); err != nil { if !settings.multiError { return err } @@ -1461,6 +1460,17 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, typ strin } } +func (schema *Schema) compilePattern() (err error) { + if schema.compiledPattern, err = regexp.Compile(schema.Pattern); err != nil { + return &SchemaError{ + Schema: schema, + SchemaField: "pattern", + Reason: fmt.Sprintf("cannot compile pattern %q: %v", schema.Pattern, err), + } + } + return nil +} + type SchemaError struct { Value interface{} reversePath []string diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go index f621ca7c9..f724f08e2 100644 --- a/openapi3/schema_test.go +++ b/openapi3/schema_test.go @@ -1219,3 +1219,13 @@ components: require.NotEqual(t, errSchema, err) require.Contains(t, err.Error(), `Error at "/ownerName": Doesn't match schema "not"`) } + +func TestValidationFailsOnInvalidPattern(t *testing.T) { + schema := Schema{ + Pattern: "[", + Type: "string", + } + + var err = schema.Validate(context.Background()) + require.Error(t, err) +}