diff --git a/go.mod b/go.mod index 93f771f..8fe88cc 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/pb33f/libopenapi-validator -go 1.19 +go 1.20 require ( - github.com/pb33f/libopenapi v0.8.0 + github.com/pb33f/libopenapi v0.8.1 github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 github.com/stretchr/testify v1.8.0 github.com/vmware-labs/yaml-jsonpath v0.3.2 diff --git a/go.sum b/go.sum index 44d36ff..4545579 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/pb33f/libopenapi v0.7.0 h1:2mQgDa0UQs8/5Hd2z+KbgWvoGx0++N3ihMKihcAS5M github.com/pb33f/libopenapi v0.7.0/go.mod h1:lvUmCtjgHUGVj6WzN3I5/CS9wkXtyN3Ykjh6ZZP5lrI= github.com/pb33f/libopenapi v0.8.0 h1:zfXNu7/Hk9sgMV3UC2hnSB4CGlgQkMaWWwPn0Id+eLI= github.com/pb33f/libopenapi v0.8.0/go.mod h1:lvUmCtjgHUGVj6WzN3I5/CS9wkXtyN3Ykjh6ZZP5lrI= +github.com/pb33f/libopenapi v0.8.1 h1:kudGljKp7cErupRr8ND5oIaTsfjOfQYAxSd3Z2dxt6k= +github.com/pb33f/libopenapi v0.8.1/go.mod h1:lvUmCtjgHUGVj6WzN3I5/CS9wkXtyN3Ykjh6ZZP5lrI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= diff --git a/parameters/cookie_parameters.go b/parameters/cookie_parameters.go index f21f00a..1ae079a 100644 --- a/parameters/cookie_parameters.go +++ b/parameters/cookie_parameters.go @@ -4,124 +4,125 @@ package parameters import ( - "github.com/pb33f/libopenapi-validator/errors" - "github.com/pb33f/libopenapi-validator/helpers" - "github.com/pb33f/libopenapi-validator/paths" - "github.com/pb33f/libopenapi/datamodel/high/base" - v3 "github.com/pb33f/libopenapi/datamodel/high/v3" - "net/http" - "strconv" - "strings" + "fmt" + "github.com/pb33f/libopenapi-validator/errors" + "github.com/pb33f/libopenapi-validator/helpers" + "github.com/pb33f/libopenapi-validator/paths" + "github.com/pb33f/libopenapi/datamodel/high/base" + v3 "github.com/pb33f/libopenapi/datamodel/high/v3" + "net/http" + "strconv" + "strings" ) func (v *paramValidator) ValidateCookieParams(request *http.Request) (bool, []*errors.ValidationError) { - // find path - var pathItem *v3.PathItem - var errs []*errors.ValidationError - if v.pathItem == nil { - pathItem, errs, _ = paths.FindPath(request, v.document) - if pathItem == nil || errs != nil { - v.errors = errs - return false, errs - } - } else { - pathItem = v.pathItem - } + // find path + var pathItem *v3.PathItem + var errs []*errors.ValidationError + if v.pathItem == nil { + pathItem, errs, _ = paths.FindPath(request, v.document) + if pathItem == nil || errs != nil { + v.errors = errs + return false, errs + } + } else { + pathItem = v.pathItem + } - // extract params for the operation - var params = helpers.ExtractParamsForOperation(request, pathItem) - var validationErrors []*errors.ValidationError - for _, p := range params { - if p.In == helpers.Cookie { - for _, cookie := range request.Cookies() { - if cookie.Name == p.Name { // cookies are case-sensitive, an exact match is required + // extract params for the operation + var params = helpers.ExtractParamsForOperation(request, pathItem) + var validationErrors []*errors.ValidationError + for _, p := range params { + if p.In == helpers.Cookie { + for _, cookie := range request.Cookies() { + if cookie.Name == p.Name { // cookies are case-sensitive, an exact match is required - var sch *base.Schema - if p.Schema != nil { - sch = p.Schema.Schema() - } - pType := sch.Type + var sch *base.Schema + if p.Schema != nil { + sch = p.Schema.Schema() + } + pType := sch.Type - for _, ty := range pType { - switch ty { - case helpers.Integer, helpers.Number: - if _, err := strconv.ParseFloat(cookie.Value, 64); err != nil { - validationErrors = append(validationErrors, - errors.InvalidCookieParamNumber(p, strings.ToLower(cookie.Value), sch)) - break - } - // check if enum is in range - if sch.Enum != nil { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(cookie.Value) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectCookieParamEnum(p, strings.ToLower(cookie.Value), sch)) - } - } - case helpers.Boolean: - if _, err := strconv.ParseBool(cookie.Value); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectCookieParamBool(p, strings.ToLower(cookie.Value), sch)) - } - case helpers.Object: - if !p.IsExploded() { - encodedObj := helpers.ConstructMapFromCSV(cookie.Value) + for _, ty := range pType { + switch ty { + case helpers.Integer, helpers.Number: + if _, err := strconv.ParseFloat(cookie.Value, 64); err != nil { + validationErrors = append(validationErrors, + errors.InvalidCookieParamNumber(p, strings.ToLower(cookie.Value), sch)) + break + } + // check if enum is in range + if sch.Enum != nil { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(cookie.Value) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectCookieParamEnum(p, strings.ToLower(cookie.Value), sch)) + } + } + case helpers.Boolean: + if _, err := strconv.ParseBool(cookie.Value); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectCookieParamBool(p, strings.ToLower(cookie.Value), sch)) + } + case helpers.Object: + if !p.IsExploded() { + encodedObj := helpers.ConstructMapFromCSV(cookie.Value) - // if a schema was extracted - if sch != nil { - validationErrors = append(validationErrors, - ValidateParameterSchema(sch, encodedObj, "", - "Cookie parameter", - "The cookie parameter", - p.Name, - helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) - } - } - case helpers.Array: + // if a schema was extracted + if sch != nil { + validationErrors = append(validationErrors, + ValidateParameterSchema(sch, encodedObj, "", + "Cookie parameter", + "The cookie parameter", + p.Name, + helpers.ParameterValidation, + helpers.ParameterValidationQuery)...) + } + } + case helpers.Array: - if !p.IsExploded() { - // well we're already in an array, so we need to check the items schema - // to ensure this array items matches the type - // only check if items is a schema, not a boolean - if sch.Items.IsA() { - validationErrors = append(validationErrors, - ValidateCookieArray(sch, p, cookie.Value)...) - } - } + if !p.IsExploded() { + // well we're already in an array, so we need to check the items schema + // to ensure this array items matches the type + // only check if items is a schema, not a boolean + if sch.Items.IsA() { + validationErrors = append(validationErrors, + ValidateCookieArray(sch, p, cookie.Value)...) + } + } - case helpers.String: + case helpers.String: - // check if the schema has an enum, and if so, match the value against one of - // the defined enum values. - if sch.Enum != nil { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(cookie.Value) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectCookieParamEnum(p, strings.ToLower(cookie.Value), sch)) - } - } - } - } - } - } - } - } - if len(validationErrors) > 0 { - return false, validationErrors - } - return true, nil + // check if the schema has an enum, and if so, match the value against one of + // the defined enum values. + if sch.Enum != nil { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(cookie.Value) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectCookieParamEnum(p, strings.ToLower(cookie.Value), sch)) + } + } + } + } + } + } + } + } + if len(validationErrors) > 0 { + return false, validationErrors + } + return true, nil } diff --git a/parameters/header_parameters.go b/parameters/header_parameters.go index c8964f9..7cfe5b1 100644 --- a/parameters/header_parameters.go +++ b/parameters/header_parameters.go @@ -4,146 +4,147 @@ package parameters import ( - "github.com/pb33f/libopenapi-validator/errors" - "github.com/pb33f/libopenapi-validator/helpers" - "github.com/pb33f/libopenapi-validator/paths" - "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/datamodel/high/v3" - "net/http" - "strconv" - "strings" + "fmt" + "github.com/pb33f/libopenapi-validator/errors" + "github.com/pb33f/libopenapi-validator/helpers" + "github.com/pb33f/libopenapi-validator/paths" + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/datamodel/high/v3" + "net/http" + "strconv" + "strings" ) func (v *paramValidator) ValidateHeaderParams(request *http.Request) (bool, []*errors.ValidationError) { - // find path - var pathItem *v3.PathItem - var errs []*errors.ValidationError - if v.pathItem == nil { - pathItem, errs, _ = paths.FindPath(request, v.document) - if pathItem == nil || errs != nil { - v.errors = errs - return false, errs - } - } else { - pathItem = v.pathItem - } - - // extract params for the operation - var params = helpers.ExtractParamsForOperation(request, pathItem) - - var validationErrors []*errors.ValidationError - var seenHeaders = make(map[string]bool) - for _, p := range params { - if p.In == helpers.Header { - - seenHeaders[strings.ToLower(p.Name)] = true - if param := request.Header.Get(p.Name); param != "" { - - var sch *base.Schema - if p.Schema != nil { - sch = p.Schema.Schema() - } - pType := sch.Type - - for _, ty := range pType { - switch ty { - case helpers.Integer, helpers.Number: - if _, err := strconv.ParseFloat(param, 64); err != nil { - validationErrors = append(validationErrors, - errors.InvalidHeaderParamNumber(p, strings.ToLower(param), sch)) - break - } - // check if the param is within the enum - if sch.Enum != nil { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(param) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectCookieParamEnum(p, strings.ToLower(param), sch)) - } - } - - case helpers.Boolean: - if _, err := strconv.ParseBool(param); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectHeaderParamBool(p, strings.ToLower(param), sch)) - } - - case helpers.Object: - - // check if the header is default encoded or not - var encodedObj map[string]interface{} - // we have found our header, check the explode type. - if p.IsDefaultHeaderEncoding() { - encodedObj = helpers.ConstructMapFromCSV(param) - } else { - if p.IsExploded() { // only option is to be exploded for KV extraction. - encodedObj = helpers.ConstructKVFromCSV(param) - } - } - - if len(encodedObj) == 0 { - validationErrors = append(validationErrors, - errors.HeaderParameterCannotBeDecoded(p, strings.ToLower(param))) - break - } - - // if a schema was extracted - if sch != nil { - validationErrors = append(validationErrors, - ValidateParameterSchema(sch, - encodedObj, - "", - "Header parameter", - "The header parameter", - p.Name, - helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) - } - - case helpers.Array: - if !p.IsExploded() { // only unexploded arrays are supported for cookie params - if sch.Items.IsA() { - validationErrors = append(validationErrors, - ValidateHeaderArray(sch, p, param)...) - } - } - - case helpers.String: - - // check if the schema has an enum, and if so, match the value against one of - // the defined enum values. - if sch.Enum != nil { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(param) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectHeaderParamEnum(p, strings.ToLower(param), sch)) - } - } - } - } - } else { - if p.Required { - validationErrors = append(validationErrors, errors.HeaderParameterMissing(p)) - } - } - } - } - - if len(validationErrors) > 0 { - return false, validationErrors - } - return true, nil + // find path + var pathItem *v3.PathItem + var errs []*errors.ValidationError + if v.pathItem == nil { + pathItem, errs, _ = paths.FindPath(request, v.document) + if pathItem == nil || errs != nil { + v.errors = errs + return false, errs + } + } else { + pathItem = v.pathItem + } + + // extract params for the operation + var params = helpers.ExtractParamsForOperation(request, pathItem) + + var validationErrors []*errors.ValidationError + var seenHeaders = make(map[string]bool) + for _, p := range params { + if p.In == helpers.Header { + + seenHeaders[strings.ToLower(p.Name)] = true + if param := request.Header.Get(p.Name); param != "" { + + var sch *base.Schema + if p.Schema != nil { + sch = p.Schema.Schema() + } + pType := sch.Type + + for _, ty := range pType { + switch ty { + case helpers.Integer, helpers.Number: + if _, err := strconv.ParseFloat(param, 64); err != nil { + validationErrors = append(validationErrors, + errors.InvalidHeaderParamNumber(p, strings.ToLower(param), sch)) + break + } + // check if the param is within the enum + if sch.Enum != nil { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(param) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectCookieParamEnum(p, strings.ToLower(param), sch)) + } + } + + case helpers.Boolean: + if _, err := strconv.ParseBool(param); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectHeaderParamBool(p, strings.ToLower(param), sch)) + } + + case helpers.Object: + + // check if the header is default encoded or not + var encodedObj map[string]interface{} + // we have found our header, check the explode type. + if p.IsDefaultHeaderEncoding() { + encodedObj = helpers.ConstructMapFromCSV(param) + } else { + if p.IsExploded() { // only option is to be exploded for KV extraction. + encodedObj = helpers.ConstructKVFromCSV(param) + } + } + + if len(encodedObj) == 0 { + validationErrors = append(validationErrors, + errors.HeaderParameterCannotBeDecoded(p, strings.ToLower(param))) + break + } + + // if a schema was extracted + if sch != nil { + validationErrors = append(validationErrors, + ValidateParameterSchema(sch, + encodedObj, + "", + "Header parameter", + "The header parameter", + p.Name, + helpers.ParameterValidation, + helpers.ParameterValidationQuery)...) + } + + case helpers.Array: + if !p.IsExploded() { // only unexploded arrays are supported for cookie params + if sch.Items.IsA() { + validationErrors = append(validationErrors, + ValidateHeaderArray(sch, p, param)...) + } + } + + case helpers.String: + + // check if the schema has an enum, and if so, match the value against one of + // the defined enum values. + if sch.Enum != nil { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(param) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectHeaderParamEnum(p, strings.ToLower(param), sch)) + } + } + } + } + } else { + if p.Required { + validationErrors = append(validationErrors, errors.HeaderParameterMissing(p)) + } + } + } + } + + if len(validationErrors) > 0 { + return false, validationErrors + } + return true, nil } diff --git a/parameters/header_parameters_test.go b/parameters/header_parameters_test.go index d7e5c90..78ea4ab 100644 --- a/parameters/header_parameters_test.go +++ b/parameters/header_parameters_test.go @@ -4,16 +4,16 @@ package parameters import ( - "github.com/pb33f/libopenapi" - "github.com/pb33f/libopenapi-validator/paths" - "github.com/stretchr/testify/assert" - "net/http" - "testing" + "github.com/pb33f/libopenapi" + "github.com/pb33f/libopenapi-validator/paths" + "github.com/stretchr/testify/assert" + "net/http" + "testing" ) func TestNewValidator_HeaderParamMissing(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /bish/bosh: get: @@ -25,22 +25,22 @@ paths: type: string ` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/bish/bosh", nil) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/bish/bosh", nil) - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "Header parameter 'bash' is missing", errors[0].Message) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "Header parameter 'bash' is missing", errors[0].Message) } func TestNewValidator_HeaderPathMissing(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /bish/bosh: get: @@ -52,22 +52,22 @@ paths: type: string ` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/I/do/not/exist", nil) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/I/do/not/exist", nil) - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "Path '/I/do/not/exist' not found", errors[0].Message) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "GET Path '/I/do/not/exist' not found", errors[0].Message) } func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -78,23 +78,23 @@ paths: schema: type: number` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "two") // headers are case-insensitive + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "two") // headers are case-insensitive - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "Header parameter 'coffeeCups' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "Header parameter 'coffeeCups' is not a valid number", errors[0].Message) } func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -105,23 +105,23 @@ paths: schema: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "two") // headers are case-insensitive + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "two") // headers are case-insensitive - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "Header parameter 'coffeeCups' is not a valid boolean", errors[0].Message) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "Header parameter 'coffeeCups' is not a valid boolean", errors[0].Message) } func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectInvalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -137,23 +137,23 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "I am not an object") // headers are case-insensitive + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "I am not an object") // headers are case-insensitive - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "Header parameter 'coffeeCups' cannot be decoded", errors[0].Message) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "Header parameter 'coffeeCups' cannot be decoded", errors[0].Message) } func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -169,23 +169,23 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "milk,true,sugar,true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "milk,true,sugar,true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "expected number, but got boolean", errors[0].SchemaValidationErrors[0].Reason) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "expected number, but got boolean", errors[0].SchemaValidationErrors[0].Reason) } func TestNewValidator_HeaderParamDefaultEncoding_InvalidParamTypeObjectBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -201,23 +201,23 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "milk,true,sugar,true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "milk,true,sugar,true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Equal(t, 1, len(errors)) - assert.Equal(t, "expected number, but got boolean", errors[0].SchemaValidationErrors[0].Reason) + assert.False(t, valid) + assert.Equal(t, 1, len(errors)) + assert.Equal(t, "expected number, but got boolean", errors[0].SchemaValidationErrors[0].Reason) } func TestNewValidator_HeaderParamDefaultEncoding_ValidParamTypeObjectBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -233,22 +233,22 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "milk,123,sugar,true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "milk,123,sugar,true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamInvalidSimpleEncoding(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -265,22 +265,22 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "milk,123,sugar,true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "milk,123,sugar,true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamNonDefaultEncoding_ValidParamTypeObject(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -297,22 +297,22 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "milk=123,sugar=true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "milk=123,sugar=true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamNonDefaultEncoding_InvalidParamTypeObject(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -329,23 +329,23 @@ paths: sugar: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "milk=true,sugar=true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "milk=true,sugar=true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "expected number, but got boolean", errors[0].SchemaValidationErrors[0].Reason) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "expected number, but got boolean", errors[0].SchemaValidationErrors[0].Reason) } func TestNewValidator_HeaderParamNonDefaultEncoding_ValidParamTypeArrayString(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -358,22 +358,22 @@ paths: items: type: string` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "1,2,3,4,5") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "1,2,3,4,5") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamNonDefaultEncoding_ValidParamTypeArrayNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -386,22 +386,22 @@ paths: items: type: number` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "1,2,3,4,5") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "1,2,3,4,5") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamNonDefaultEncoding_ValidParamTypeArrayBool(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -414,22 +414,22 @@ paths: items: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "true,false,true,false,true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "true,false,true,false,true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamNonDefaultEncoding_InvalidParamTypeArrayNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -442,22 +442,22 @@ paths: items: type: number` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "true,false,true,false,true") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "true,false,true,false,true") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Len(t, errors, 5) + assert.False(t, valid) + assert.Len(t, errors, 5) } func TestNewValidator_HeaderParamNonDefaultEncoding_InvalidParamTypeArrayBool(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -470,22 +470,22 @@ paths: items: type: boolean` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "1,false,2,true,5,false") // default encoding. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "1,false,2,true,5,false") // default encoding. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Len(t, errors, 3) + assert.False(t, valid) + assert.Len(t, errors, 3) } func TestNewValidator_HeaderParamStringValidEnum(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -497,22 +497,22 @@ paths: type: string enum: [glass, china, thermos]` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "glass") + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "glass") - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamStringInvalidEnum(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -524,24 +524,24 @@ paths: type: string enum: [glass, china, thermos]` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "microwave") // this is not a cup! + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "microwave") // this is not a cup! - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Instead of 'microwave', "+ - "use one of the allowed values: 'glass, china, thermos'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Instead of 'microwave', "+ + "use one of the allowed values: 'glass, china, thermos'", errors[0].HowToFix) } func TestNewValidator_HeaderParamIntegerValidEnum(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -553,22 +553,22 @@ paths: type: integer enum: [1,2,99]` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "2") + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "2") - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_HeaderParamNumberInvalidEnum(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -580,24 +580,24 @@ paths: type: integer enum: [1,2,99]` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "1200") // that's a lot of cups dude, we only have one dishwasher. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "1200") // that's a lot of cups dude, we only have one dishwasher. - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Instead of '1200', "+ - "use one of the allowed values: '1, 2, 99'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Instead of '1200', "+ + "use one of the allowed values: '1, 2, 99'", errors[0].HowToFix) } func TestNewValidator_HeaderParamSetPath(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /vending/drinks: get: @@ -609,21 +609,21 @@ paths: type: integer enum: [1,2,99]` - doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) - request.Header.Set("coffeecups", "1200") // that's a lot of cups dude, we only have one dishwasher. + request, _ := http.NewRequest(http.MethodGet, "https://things.com/vending/drinks", nil) + request.Header.Set("coffeecups", "1200") // that's a lot of cups dude, we only have one dishwasher. - // preset the path - path, _, pv := paths.FindPath(request, &m.Model) - v.SetPathItem(path, pv) + // preset the path + path, _, pv := paths.FindPath(request, &m.Model) + v.SetPathItem(path, pv) - valid, errors := v.ValidateHeaderParams(request) + valid, errors := v.ValidateHeaderParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Instead of '1200', "+ - "use one of the allowed values: '1, 2, 99'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Instead of '1200', "+ + "use one of the allowed values: '1, 2, 99'", errors[0].HowToFix) } diff --git a/parameters/path_parameters.go b/parameters/path_parameters.go index a0a0dbd..2307048 100644 --- a/parameters/path_parameters.go +++ b/parameters/path_parameters.go @@ -4,270 +4,270 @@ package parameters import ( - "fmt" - "github.com/pb33f/libopenapi-validator/errors" - "github.com/pb33f/libopenapi-validator/helpers" - "github.com/pb33f/libopenapi-validator/paths" - "github.com/pb33f/libopenapi/datamodel/high/v3" - "net/http" - "strconv" - "strings" + "fmt" + "github.com/pb33f/libopenapi-validator/errors" + "github.com/pb33f/libopenapi-validator/helpers" + "github.com/pb33f/libopenapi-validator/paths" + "github.com/pb33f/libopenapi/datamodel/high/v3" + "net/http" + "strconv" + "strings" ) func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError) { - // find path - var pathItem *v3.PathItem - var errs []*errors.ValidationError - var foundPath string - if v.pathItem == nil && v.pathValue == "" { - pathItem, errs, foundPath = paths.FindPath(request, v.document) - if pathItem == nil || errs != nil { - v.errors = errs - return false, errs - } - } else { - pathItem = v.pathItem - foundPath = v.pathValue - } + // find path + var pathItem *v3.PathItem + var errs []*errors.ValidationError + var foundPath string + if v.pathItem == nil && v.pathValue == "" { + pathItem, errs, foundPath = paths.FindPath(request, v.document) + if pathItem == nil || errs != nil { + v.errors = errs + return false, errs + } + } else { + pathItem = v.pathItem + foundPath = v.pathValue + } - // extract params for the operation - var params = helpers.ExtractParamsForOperation(request, pathItem) - var validationErrors []*errors.ValidationError - for _, p := range params { - if p.In == helpers.Path { + // extract params for the operation + var params = helpers.ExtractParamsForOperation(request, pathItem) + var validationErrors []*errors.ValidationError + for _, p := range params { + if p.In == helpers.Path { - // split the path into segments - submittedSegments := strings.Split(request.URL.Path, helpers.Slash) - pathSegments := strings.Split(foundPath, helpers.Slash) + // split the path into segments + submittedSegments := strings.Split(request.URL.Path, helpers.Slash) + pathSegments := strings.Split(foundPath, helpers.Slash) - //var paramTemplate string - for x := range pathSegments { - if pathSegments[x] == "" { // skip empty segments - continue - } - i := strings.IndexRune(pathSegments[x], '{') - if i > -1 { - isMatrix := false - isLabel := false - //isExplode := false - isSimple := true - paramTemplate := pathSegments[x][i+1 : len(pathSegments[x])-1] - paramName := paramTemplate - // check for an asterisk on the end of the parameter (explode) - if strings.HasSuffix(paramTemplate, helpers.Asterisk) { - //isExplode = true - paramName = paramTemplate[:len(paramTemplate)-1] - } - if strings.HasPrefix(paramTemplate, helpers.Period) { - isLabel = true - isSimple = false - paramName = paramName[1:] - } - if strings.HasPrefix(paramTemplate, helpers.SemiColon) { - isMatrix = true - isSimple = false - paramName = paramName[1:] - } + //var paramTemplate string + for x := range pathSegments { + if pathSegments[x] == "" { // skip empty segments + continue + } + i := strings.IndexRune(pathSegments[x], '{') + if i > -1 { + isMatrix := false + isLabel := false + //isExplode := false + isSimple := true + paramTemplate := pathSegments[x][i+1 : len(pathSegments[x])-1] + paramName := paramTemplate + // check for an asterisk on the end of the parameter (explode) + if strings.HasSuffix(paramTemplate, helpers.Asterisk) { + //isExplode = true + paramName = paramTemplate[:len(paramTemplate)-1] + } + if strings.HasPrefix(paramTemplate, helpers.Period) { + isLabel = true + isSimple = false + paramName = paramName[1:] + } + if strings.HasPrefix(paramTemplate, helpers.SemiColon) { + isMatrix = true + isSimple = false + paramName = paramName[1:] + } - // does this param name match the current path segment param name - if paramName != p.Name { - continue - } + // does this param name match the current path segment param name + if paramName != p.Name { + continue + } - // extract the parameter value from the path. - paramValue := submittedSegments[x] + // extract the parameter value from the path. + paramValue := submittedSegments[x] - // extract the schema from the parameter - sch := p.Schema.Schema() + // extract the schema from the parameter + sch := p.Schema.Schema() - // check enum (if present) - enumCheck := func(paramValue string) { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(paramValue) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectPathParamEnum(p, strings.ToLower(paramValue), sch)) - } - } + // check enum (if present) + enumCheck := func(paramValue string) { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(paramValue) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectPathParamEnum(p, strings.ToLower(paramValue), sch)) + } + } - // for each type, check the value. - for typ := range sch.Type { + // for each type, check the value. + for typ := range sch.Type { - switch sch.Type[typ] { - case helpers.String: + switch sch.Type[typ] { + case helpers.String: - // TODO: label and matrix style validation + // TODO: label and matrix style validation - // check if the param is within the enum - if sch.Enum != nil { - enumCheck(paramValue) - } + // check if the param is within the enum + if sch.Enum != nil { + enumCheck(paramValue) + } - case helpers.Integer, helpers.Number: - // simple use case is already handled in find param. - if isLabel && p.Style == helpers.LabelStyle { - if _, err := strconv.ParseFloat(paramValue[1:], 64); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamNumber(p, paramValue[1:], sch)) - break - } - // check if the param is within the enum - if sch.Enum != nil { - enumCheck(paramValue[1:]) - break - } - } - if isMatrix && p.Style == helpers.MatrixStyle { - // strip off the colon and the parameter name - paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) - if _, err := strconv.ParseFloat(paramValue, 64); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamNumber(p, paramValue[1:], sch)) - break - } - // check if the param is within the enum - if sch.Enum != nil { - enumCheck(paramValue) - break - } - } - // check if the param is within the enum - if sch.Enum != nil { - enumCheck(paramValue) - break - } + case helpers.Integer, helpers.Number: + // simple use case is already handled in find param. + if isLabel && p.Style == helpers.LabelStyle { + if _, err := strconv.ParseFloat(paramValue[1:], 64); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamNumber(p, paramValue[1:], sch)) + break + } + // check if the param is within the enum + if sch.Enum != nil { + enumCheck(paramValue[1:]) + break + } + } + if isMatrix && p.Style == helpers.MatrixStyle { + // strip off the colon and the parameter name + paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) + if _, err := strconv.ParseFloat(paramValue, 64); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamNumber(p, paramValue[1:], sch)) + break + } + // check if the param is within the enum + if sch.Enum != nil { + enumCheck(paramValue) + break + } + } + // check if the param is within the enum + if sch.Enum != nil { + enumCheck(paramValue) + break + } - case helpers.Boolean: - if isLabel && p.Style == helpers.LabelStyle { - if _, err := strconv.ParseFloat(paramValue[1:], 64); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamBool(p, paramValue[1:], sch)) - } - } - if isSimple { - if _, err := strconv.ParseBool(paramValue); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamBool(p, paramValue, sch)) - } - } - if isMatrix && p.Style == helpers.MatrixStyle { - // strip off the colon and the parameter name - paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) - if _, err := strconv.ParseBool(paramValue); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamBool(p, paramValue, sch)) - } - } - case helpers.Object: - var encodedObject interface{} + case helpers.Boolean: + if isLabel && p.Style == helpers.LabelStyle { + if _, err := strconv.ParseFloat(paramValue[1:], 64); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamBool(p, paramValue[1:], sch)) + } + } + if isSimple { + if _, err := strconv.ParseBool(paramValue); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamBool(p, paramValue, sch)) + } + } + if isMatrix && p.Style == helpers.MatrixStyle { + // strip off the colon and the parameter name + paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) + if _, err := strconv.ParseBool(paramValue); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamBool(p, paramValue, sch)) + } + } + case helpers.Object: + var encodedObject interface{} - if p.IsDefaultPathEncoding() { - encodedObject = helpers.ConstructMapFromCSV(paramValue) - } else { - switch p.Style { - case helpers.LabelStyle: - if !p.IsExploded() { - encodedObject = helpers.ConstructMapFromCSV(paramValue[1:]) - } else { - encodedObject = helpers.ConstructKVFromLabelEncoding(paramValue) - } - case helpers.MatrixStyle: - if !p.IsExploded() { - paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) - encodedObject = helpers.ConstructMapFromCSV(paramValue) - } else { - paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) - encodedObject = helpers.ConstructKVFromMatrixCSV(paramValue) - } - default: - if p.IsExploded() { - encodedObject = helpers.ConstructKVFromCSV(paramValue) - } - } - } - // if a schema was extracted - if sch != nil { - validationErrors = append(validationErrors, - ValidateParameterSchema(sch, - encodedObject, - "", - "Path parameter", - "The path parameter", - p.Name, - helpers.ParameterValidation, - helpers.ParameterValidationPath)...) - } + if p.IsDefaultPathEncoding() { + encodedObject = helpers.ConstructMapFromCSV(paramValue) + } else { + switch p.Style { + case helpers.LabelStyle: + if !p.IsExploded() { + encodedObject = helpers.ConstructMapFromCSV(paramValue[1:]) + } else { + encodedObject = helpers.ConstructKVFromLabelEncoding(paramValue) + } + case helpers.MatrixStyle: + if !p.IsExploded() { + paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) + encodedObject = helpers.ConstructMapFromCSV(paramValue) + } else { + paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) + encodedObject = helpers.ConstructKVFromMatrixCSV(paramValue) + } + default: + if p.IsExploded() { + encodedObject = helpers.ConstructKVFromCSV(paramValue) + } + } + } + // if a schema was extracted + if sch != nil { + validationErrors = append(validationErrors, + ValidateParameterSchema(sch, + encodedObject, + "", + "Path parameter", + "The path parameter", + p.Name, + helpers.ParameterValidation, + helpers.ParameterValidationPath)...) + } - case helpers.Array: + case helpers.Array: - // extract the items schema in order to validate the array items. - if sch.Items != nil && sch.Items.IsA() { - iSch := sch.Items.A.Schema() - for n := range iSch.Type { - // determine how to explode the array - var arrayValues []string - if isSimple { - arrayValues = strings.Split(paramValue, helpers.Comma) - } - if isLabel { - if !p.IsExploded() { - arrayValues = strings.Split(paramValue[1:], helpers.Comma) - } else { - arrayValues = strings.Split(paramValue[1:], helpers.Period) - } - } - if isMatrix { - if !p.IsExploded() { - paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) - arrayValues = strings.Split(paramValue, helpers.Comma) - } else { - paramValue = strings.ReplaceAll(paramValue[1:], fmt.Sprintf("%s=", p.Name), "") - arrayValues = strings.Split(paramValue, helpers.SemiColon) - } - } - switch iSch.Type[n] { - case helpers.Integer, helpers.Number: - for pv := range arrayValues { - if _, err := strconv.ParseFloat(arrayValues[pv], 64); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamArrayNumber(p, arrayValues[pv], sch, iSch)) - } - } - case helpers.Boolean: - for pv := range arrayValues { - bc := len(validationErrors) - if _, err := strconv.ParseBool(arrayValues[pv]); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectPathParamArrayBoolean(p, arrayValues[pv], sch, iSch)) - continue - } - if len(validationErrors) == bc { - // ParseBool will parse 0 or 1 as false/true to we - // need to catch this edge case. - if arrayValues[pv] == "0" || arrayValues[pv] == "1" { - validationErrors = append(validationErrors, - errors.IncorrectPathParamArrayBoolean(p, arrayValues[pv], sch, iSch)) - continue - } - } - } - } - } - } - } - } - } - } - } - } - if len(validationErrors) > 0 { - return false, validationErrors - } - return true, nil + // extract the items schema in order to validate the array items. + if sch.Items != nil && sch.Items.IsA() { + iSch := sch.Items.A.Schema() + for n := range iSch.Type { + // determine how to explode the array + var arrayValues []string + if isSimple { + arrayValues = strings.Split(paramValue, helpers.Comma) + } + if isLabel { + if !p.IsExploded() { + arrayValues = strings.Split(paramValue[1:], helpers.Comma) + } else { + arrayValues = strings.Split(paramValue[1:], helpers.Period) + } + } + if isMatrix { + if !p.IsExploded() { + paramValue = strings.Replace(paramValue[1:], fmt.Sprintf("%s=", p.Name), "", 1) + arrayValues = strings.Split(paramValue, helpers.Comma) + } else { + paramValue = strings.ReplaceAll(paramValue[1:], fmt.Sprintf("%s=", p.Name), "") + arrayValues = strings.Split(paramValue, helpers.SemiColon) + } + } + switch iSch.Type[n] { + case helpers.Integer, helpers.Number: + for pv := range arrayValues { + if _, err := strconv.ParseFloat(arrayValues[pv], 64); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamArrayNumber(p, arrayValues[pv], sch, iSch)) + } + } + case helpers.Boolean: + for pv := range arrayValues { + bc := len(validationErrors) + if _, err := strconv.ParseBool(arrayValues[pv]); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectPathParamArrayBoolean(p, arrayValues[pv], sch, iSch)) + continue + } + if len(validationErrors) == bc { + // ParseBool will parse 0 or 1 as false/true to we + // need to catch this edge case. + if arrayValues[pv] == "0" || arrayValues[pv] == "1" { + validationErrors = append(validationErrors, + errors.IncorrectPathParamArrayBoolean(p, arrayValues[pv], sch, iSch)) + continue + } + } + } + } + } + } + } + } + } + } + } + } + if len(validationErrors) > 0 { + return false, validationErrors + } + return true, nil } diff --git a/parameters/path_parameters_test.go b/parameters/path_parameters_test.go index cba1906..b7cc1bb 100644 --- a/parameters/path_parameters_test.go +++ b/parameters/path_parameters_test.go @@ -4,16 +4,16 @@ package parameters import ( - "github.com/pb33f/libopenapi" - "github.com/pb33f/libopenapi-validator/paths" - "github.com/stretchr/testify/assert" - "net/http" - "testing" + "github.com/pb33f/libopenapi" + "github.com/pb33f/libopenapi-validator/paths" + "github.com/stretchr/testify/assert" + "net/http" + "testing" ) func TestNewValidator_SimpleArrayEncodedPath(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerIds*}/locate: parameters: @@ -26,22 +26,22 @@ paths: patch: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodPatch, "https://things.com/burgers/1,2,3,4,5/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodPatch, "https://things.com/burgers/1,2,3,4,5/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_SimpleArrayEncodedPath_InvalidNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerIds*}/locate: parameters: @@ -54,23 +54,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/1,pizza,3,4,false/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/1,pizza,3,4,false/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 2) - assert.Equal(t, "Path array parameter 'burgerIds' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 2) + assert.Equal(t, "Path array parameter 'burgerIds' is not a valid number", errors[0].Message) } func TestNewValidator_SimpleArrayEncodedPath_InvalidBool(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerIds*}/locate: parameters: @@ -83,23 +83,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/1,true,0,frogs,false/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/1,true,0,frogs,false/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 3) - assert.Equal(t, "Path array parameter 'burgerIds' is not a valid boolean", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 3) + assert.Equal(t, "Path array parameter 'burgerIds' is not a valid boolean", errors[0].Message) } func TestNewValidator_SimpleObjectEncodedPath(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burger}/locate: parameters: @@ -115,22 +115,22 @@ paths: get: operationId: locateBurger` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id,1234,vegetarian,true/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id,1234,vegetarian,true/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_SimpleObjectEncodedPath_Invalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burger}/locate: parameters: @@ -146,23 +146,23 @@ paths: get: operationId: locateBurger` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id,hello,vegetarian,there/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id,hello,vegetarian,there/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Len(t, errors[0].SchemaValidationErrors, 2) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Len(t, errors[0].SchemaValidationErrors, 2) } func TestNewValidator_SimpleObjectEncodedPath_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burger}/locate: parameters: @@ -179,22 +179,22 @@ paths: get: operationId: locateBurger` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id=1234,vegetarian=true/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id=1234,vegetarian=true/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_SimpleObjectEncodedPath_ExplodedInvalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burger}/locate: parameters: @@ -211,23 +211,23 @@ paths: get: operationId: locateBurger` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id=toast,vegetarian=chicken/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id=toast,vegetarian=chicken/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Len(t, errors[0].SchemaValidationErrors, 2) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Len(t, errors[0].SchemaValidationErrors, 2) } func TestNewValidator_ObjectEncodedPath(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burger}/locate: parameters: @@ -243,22 +243,22 @@ paths: get: operationId: locateBurger` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id,1234,vegetarian,true/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/id,1234,vegetarian,true/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_SimpleEncodedPath_InvalidInteger(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerId}/locate: parameters: @@ -269,23 +269,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/hello/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/hello/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path '/burgers/hello/locate' not found", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "GET Path '/burgers/hello/locate' not found", errors[0].Message) } func TestNewValidator_SimpleEncodedPath_InvalidBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerId}/locate: parameters: @@ -296,23 +296,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/hello/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/hello/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' is not a valid boolean", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' is not a valid boolean", errors[0].Message) } func TestNewValidator_LabelEncodedPath_InvalidInteger(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -324,23 +324,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.hello/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.hello/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message) } func TestNewValidator_LabelEncodedPath_InvalidBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -352,23 +352,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.hello/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.hello/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' is not a valid boolean", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' is not a valid boolean", errors[0].Message) } func TestNewValidator_LabelEncodedPath_ValidArray_Number(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -382,22 +382,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3,4,5,6/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3,4,5,6/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_LabelEncodedPath_ValidArray_Number_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -412,22 +412,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3.4.5.6/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3.4.5.6/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_LabelEncodedPath_InvalidArray_Number_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -442,23 +442,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3.Not a number.5.6/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3.Not a number.5.6/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path array parameter 'burgerId' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path array parameter 'burgerId' is not a valid number", errors[0].Message) } func TestNewValidator_LabelEncodedPath_InvalidArray_Number(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -472,23 +472,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3,4,Not a number,6/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.3,4,Not a number,6/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path array parameter 'burgerId' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path array parameter 'burgerId' is not a valid number", errors[0].Message) } func TestNewValidator_LabelEncodedPath_InvalidObject(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -505,23 +505,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id,hello,vegetarian,why/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id,hello,vegetarian,why/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Len(t, errors[0].SchemaValidationErrors, 2) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Len(t, errors[0].SchemaValidationErrors, 2) } func TestNewValidator_LabelEncodedPath_InvalidObject_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -539,23 +539,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id=hello.vegetarian=why/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id=hello.vegetarian=why/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Len(t, errors[0].SchemaValidationErrors, 2) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Len(t, errors[0].SchemaValidationErrors, 2) } func TestNewValidator_LabelEncodedPath_ValidMultiParam(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate/{.query}: parameters: @@ -578,22 +578,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id=1234.vegetarian=true/locate/bigMac", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id=1234.vegetarian=true/locate/bigMac", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_LabelEncodedPath_InvalidMultiParam(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate/{.query}: parameters: @@ -616,22 +616,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id=1234.vegetarian=true/locate/bigMac", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.id=1234.vegetarian=true/locate/bigMac", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) + assert.False(t, valid) + assert.Len(t, errors, 1) } func TestNewValidator_MatrixEncodedPath_ValidPrimitiveNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burgerId}/locate: parameters: @@ -643,22 +643,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=5/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=5/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_MatrixEncodedPath_InvalidPrimitiveNumber(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burgerId}/locate: parameters: @@ -670,23 +670,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=I am not a number/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=I am not a number/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' is not a valid number", errors[0].Message) } func TestNewValidator_MatrixEncodedPath_ValidPrimitiveBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burgerId}/locate: parameters: @@ -698,22 +698,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=false/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=false/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_MatrixEncodedPath_InvalidPrimitiveBoolean(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burgerId}/locate: parameters: @@ -725,24 +725,24 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=I am also not a bool/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=I am also not a bool/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' is not a valid boolean", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' is not a valid boolean", errors[0].Message) } func TestNewValidator_MatrixEncodedPath_ValidObject(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger}/locate: parameters: @@ -759,23 +759,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=id,1234,vegetarian,false/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=id,1234,vegetarian,false/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_MatrixEncodedPath_InvalidObject(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger}/locate: parameters: @@ -792,23 +792,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=id,1234,vegetarian,I am not a bool/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=id,1234,vegetarian,I am not a bool/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "expected boolean, but got string", errors[0].SchemaValidationErrors[0].Reason) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "expected boolean, but got string", errors[0].SchemaValidationErrors[0].Reason) } func TestNewValidator_MatrixEncodedPath_ValidObject_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -826,22 +826,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;id=1234;vegetarian=false/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;id=1234;vegetarian=false/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_MatrixEncodedPath_InvalidObject_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -859,22 +859,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;id=1234;vegetarian=I am not a boolean/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;id=1234;vegetarian=I am not a boolean/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "expected boolean, but got string", errors[0].SchemaValidationErrors[0].Reason) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "expected boolean, but got string", errors[0].SchemaValidationErrors[0].Reason) } func TestNewValidator_MatrixEncodedPath_ValidArray(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -888,22 +888,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1,2,3,4,5/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1,2,3,4,5/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_MatrixEncodedPath_InvalidArray(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -917,21 +917,21 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1,2,not a number,4,false/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1,2,not a number,4,false/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 2) + assert.False(t, valid) + assert.Len(t, errors, 2) } func TestNewValidator_MatrixEncodedPath_ValidArray_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -946,21 +946,21 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1;burger=2;burger=3/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1;burger=2;burger=3/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_MatrixEncodedPath_InvalidArray_Exploded(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -975,22 +975,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1;burger=I am not an int;burger=3/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burger=1;burger=I am not an int;burger=3/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path array parameter 'burger' is not a valid number", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path array parameter 'burger' is not a valid number", errors[0].Message) } func TestNewValidator_PathParams_PathNotFound(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burger*}/locate: parameters: @@ -1005,21 +1005,21 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + m, _ := doc.BuildV3Model() + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/I do not exist", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/I do not exist", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) + assert.False(t, valid) + assert.Len(t, errors, 1) } func TestNewValidator_PathParamStringEnumValid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerId}/locate: parameters: @@ -1031,23 +1031,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/bigMac/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/bigMac/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_PathParamStringEnumInvalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerId}/locate: parameters: @@ -1059,25 +1059,25 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/hello/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/hello/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) - assert.Equal(t, "Instead of 'hello', use one of the allowed values: 'bigMac, whopper, mcCrispy'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) + assert.Equal(t, "Instead of 'hello', use one of the allowed values: 'bigMac, whopper, mcCrispy'", errors[0].HowToFix) } func TestNewValidator_PathParamIntegerEnumValid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerId}/locate: parameters: @@ -1089,22 +1089,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/2/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/2/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_PathParamIntegerEnumInvalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{burgerId}/locate: parameters: @@ -1116,23 +1116,23 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/3284/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/3284/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) } func TestNewValidator_PathLabelEumValid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -1145,22 +1145,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.2/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.2/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.True(t, valid) - assert.Len(t, errors, 0) + assert.True(t, valid) + assert.Len(t, errors, 0) } func TestNewValidator_PathLabelEumInvalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{.burgerId}/locate: parameters: @@ -1173,24 +1173,24 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.22334/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/.22334/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) - assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) + assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix) } func TestNewValidator_PathMatrixEumInvalid(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burgerId}/locate: parameters: @@ -1203,24 +1203,24 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=22334/locate", nil) - valid, errors := v.ValidatePathParams(request) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=22334/locate", nil) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) - assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) + assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix) } func TestNewValidator_SetPathForPathParam(t *testing.T) { - spec := `openapi: 3.1.0 + spec := `openapi: 3.1.0 paths: /burgers/{;burgerId}/locate: parameters: @@ -1233,22 +1233,22 @@ paths: get: operationId: locateBurgers` - doc, _ := libopenapi.NewDocument([]byte(spec)) + doc, _ := libopenapi.NewDocument([]byte(spec)) - m, _ := doc.BuildV3Model() + m, _ := doc.BuildV3Model() - v := NewParameterValidator(&m.Model) + v := NewParameterValidator(&m.Model) - request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=22334/locate", nil) + request, _ := http.NewRequest(http.MethodGet, "https://things.com/burgers/;burgerId=22334/locate", nil) - // preset the path - path, _, pv := paths.FindPath(request, &m.Model) - v.SetPathItem(path, pv) + // preset the path + path, _, pv := paths.FindPath(request, &m.Model) + v.SetPathItem(path, pv) - valid, errors := v.ValidatePathParams(request) + valid, errors := v.ValidatePathParams(request) - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) - assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix) + assert.False(t, valid) + assert.Len(t, errors, 1) + assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message) + assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix) } diff --git a/parameters/query_parameters.go b/parameters/query_parameters.go index d9a1d35..165d463 100644 --- a/parameters/query_parameters.go +++ b/parameters/query_parameters.go @@ -4,245 +4,246 @@ package parameters import ( - "encoding/json" - "github.com/pb33f/libopenapi-validator/errors" - "github.com/pb33f/libopenapi-validator/helpers" - "github.com/pb33f/libopenapi-validator/paths" - "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/datamodel/high/v3" - "net/http" - "regexp" - "strconv" - "strings" + "encoding/json" + "fmt" + "github.com/pb33f/libopenapi-validator/errors" + "github.com/pb33f/libopenapi-validator/helpers" + "github.com/pb33f/libopenapi-validator/paths" + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/datamodel/high/v3" + "net/http" + "regexp" + "strconv" + "strings" ) func (v *paramValidator) ValidateQueryParams(request *http.Request) (bool, []*errors.ValidationError) { - // find path - var pathItem *v3.PathItem - var errs []*errors.ValidationError - if v.pathItem == nil { - pathItem, errs, _ = paths.FindPath(request, v.document) - if pathItem == nil || errs != nil { - v.errors = errs - return false, errs - } - } else { - pathItem = v.pathItem - } - - // extract params for the operation - var params = helpers.ExtractParamsForOperation(request, pathItem) - queryParams := make(map[string][]*helpers.QueryParam) - var validationErrors []*errors.ValidationError - - for qKey, qVal := range request.URL.Query() { - // check if the param is encoded as a property / deepObject - if strings.IndexRune(qKey, '[') > 0 && strings.IndexRune(qKey, ']') > 0 { - stripped := qKey[:strings.IndexRune(qKey, '[')] - value := qKey[strings.IndexRune(qKey, '[')+1 : strings.IndexRune(qKey, ']')] - queryParams[stripped] = append(queryParams[stripped], &helpers.QueryParam{ - Key: stripped, - Values: qVal, - Property: value, - }) - } else { - queryParams[qKey] = append(queryParams[qKey], &helpers.QueryParam{ - Key: qKey, - Values: qVal, - }) - } - } - - // look through the params for the query key + // find path + var pathItem *v3.PathItem + var errs []*errors.ValidationError + if v.pathItem == nil { + pathItem, errs, _ = paths.FindPath(request, v.document) + if pathItem == nil || errs != nil { + v.errors = errs + return false, errs + } + } else { + pathItem = v.pathItem + } + + // extract params for the operation + var params = helpers.ExtractParamsForOperation(request, pathItem) + queryParams := make(map[string][]*helpers.QueryParam) + var validationErrors []*errors.ValidationError + + for qKey, qVal := range request.URL.Query() { + // check if the param is encoded as a property / deepObject + if strings.IndexRune(qKey, '[') > 0 && strings.IndexRune(qKey, ']') > 0 { + stripped := qKey[:strings.IndexRune(qKey, '[')] + value := qKey[strings.IndexRune(qKey, '[')+1 : strings.IndexRune(qKey, ']')] + queryParams[stripped] = append(queryParams[stripped], &helpers.QueryParam{ + Key: stripped, + Values: qVal, + Property: value, + }) + } else { + queryParams[qKey] = append(queryParams[qKey], &helpers.QueryParam{ + Key: qKey, + Values: qVal, + }) + } + } + + // look through the params for the query key doneLooking: - for p := range params { - if params[p].In == helpers.Query { - - contentWrapped := false - var contentType string - // check if this param is found as a set of query strings - if jk, ok := queryParams[params[p].Name]; ok { - skipValues: - for _, fp := range jk { - // let's check styles first. - validationErrors = append(validationErrors, ValidateQueryParamStyle(params[p], jk)...) - - // there is a match, is the type correct - // this context is extracted from the 3.1 spec to explain what is going on here: - // For more complex scenarios, the content property can define the media type and schema of the - // parameter. A parameter MUST contain either a schema property, or a content property, but not both. - // The map MUST only contain one entry. (for content) - var sch *base.Schema - if params[p].Schema != nil { - sch = params[p].Schema.Schema() - } else { - // ok, no schema, check for a content type - if params[p].Content != nil { - for k, ct := range params[p].Content { - sch = ct.Schema.Schema() - contentWrapped = true - contentType = k - break - } - } - } - pType := sch.Type - - // for each param, check each type - for _, ef := range fp.Values { - - // check allowReserved values. If this is set to true, then we can allow the - // following characters - // :/?#[]@!$&'()*+,;= - // to be present as they are, without being URLEncoded. - if !params[p].AllowReserved { - rx := `[:\/\?#\[\]\@!\$&'\(\)\*\+,;=]` - regexp.MustCompile(rx) - if regexp.MustCompile(rx).MatchString(ef) && params[p].IsExploded() { - validationErrors = append(validationErrors, - errors.IncorrectReservedValues(params[p], ef, sch)) - } - } - for _, ty := range pType { - switch ty { - - case helpers.String: - - // check if the param is within an enum - if sch.Enum != nil { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(ef) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectQueryParamEnum(params[p], ef, sch)) - } - } - - case helpers.Integer, helpers.Number: - if _, err := strconv.ParseFloat(ef, 64); err != nil { - validationErrors = append(validationErrors, - errors.InvalidQueryParamNumber(params[p], ef, sch)) - break - } - // check if the param is within an enum - if sch.Enum != nil { - matchFound := false - for _, enumVal := range sch.Enum { - if strings.TrimSpace(ef) == enumVal { - matchFound = true - break - } - } - if !matchFound { - validationErrors = append(validationErrors, - errors.IncorrectQueryParamEnum(params[p], ef, sch)) - } - } - - case helpers.Boolean: - if _, err := strconv.ParseBool(ef); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectQueryParamBool(params[p], ef, sch)) - } - case helpers.Object: - - // check what style of encoding was used and then construct a map[string]interface{} - // and pass that in as encoded JSON. - var encodedObj map[string]interface{} - - switch params[p].Style { - case helpers.DeepObject: - encodedObj = helpers.ConstructParamMapFromDeepObjectEncoding(jk) - case helpers.PipeDelimited: - encodedObj = helpers.ConstructParamMapFromPipeEncoding(jk) - case helpers.SpaceDelimited: - encodedObj = helpers.ConstructParamMapFromSpaceEncoding(jk) - default: - // form encoding is default. - if contentWrapped { - switch contentType { - case helpers.JSONContentType: - // we need to unmarshal the JSON into a map[string]interface{} - encodedParams := make(map[string]interface{}) - encodedObj = make(map[string]interface{}) - if err := json.Unmarshal([]byte(ef), &encodedParams); err != nil { - validationErrors = append(validationErrors, - errors.IncorrectParamEncodingJSON(params[p], ef, sch)) - break skipValues - } - encodedObj[params[p].Name] = encodedParams - } - } else { - encodedObj = helpers.ConstructParamMapFromFormEncodingArray(jk) - } - } - - numErrors := len(validationErrors) - validationErrors = append(validationErrors, - ValidateParameterSchema(sch, encodedObj[params[p].Name].(map[string]interface{}), - ef, - "Query parameter", - "The query parameter", - params[p].Name, - helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) - if len(validationErrors) > numErrors { - // we've already added an error for this, so we can skip the rest of the values - break skipValues - } - - case helpers.Array: - // well we're already in an array, so we need to check the items schema - // to ensure this array items matches the type - // only check if items is a schema, not a boolean - if sch.Items.IsA() { - validationErrors = append(validationErrors, - ValidateQueryArray(sch, params[p], ef, contentWrapped)...) - } - } - } - } - } - - } else { - // if the param is not in the requests, so let's check if this param is an - // object, and if we should use default encoding and explode values. - if params[p].Schema != nil { - sch := params[p].Schema.Schema() - - if sch.Type[0] == helpers.Object && params[p].IsDefaultFormEncoding() { - // if the param is an object, and we're using default encoding, then we need to - // validate the schema. - decoded := helpers.ConstructParamMapFromQueryParamInput(queryParams) - validationErrors = append(validationErrors, - ValidateParameterSchema(sch, - decoded, - "", - "Query array parameter", - "The query parameter (which is an array)", - params[p].Name, - helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) - break doneLooking - } - } - // if there is no match, check if the param is required or not. - if params[p].Required { - validationErrors = append(validationErrors, errors.QueryParameterMissing(params[p])) - } - } - } - } - - v.errors = validationErrors - if len(validationErrors) > 0 { - return false, validationErrors - } - return true, nil + for p := range params { + if params[p].In == helpers.Query { + + contentWrapped := false + var contentType string + // check if this param is found as a set of query strings + if jk, ok := queryParams[params[p].Name]; ok { + skipValues: + for _, fp := range jk { + // let's check styles first. + validationErrors = append(validationErrors, ValidateQueryParamStyle(params[p], jk)...) + + // there is a match, is the type correct + // this context is extracted from the 3.1 spec to explain what is going on here: + // For more complex scenarios, the content property can define the media type and schema of the + // parameter. A parameter MUST contain either a schema property, or a content property, but not both. + // The map MUST only contain one entry. (for content) + var sch *base.Schema + if params[p].Schema != nil { + sch = params[p].Schema.Schema() + } else { + // ok, no schema, check for a content type + if params[p].Content != nil { + for k, ct := range params[p].Content { + sch = ct.Schema.Schema() + contentWrapped = true + contentType = k + break + } + } + } + pType := sch.Type + + // for each param, check each type + for _, ef := range fp.Values { + + // check allowReserved values. If this is set to true, then we can allow the + // following characters + // :/?#[]@!$&'()*+,;= + // to be present as they are, without being URLEncoded. + if !params[p].AllowReserved { + rx := `[:\/\?#\[\]\@!\$&'\(\)\*\+,;=]` + regexp.MustCompile(rx) + if regexp.MustCompile(rx).MatchString(ef) && params[p].IsExploded() { + validationErrors = append(validationErrors, + errors.IncorrectReservedValues(params[p], ef, sch)) + } + } + for _, ty := range pType { + switch ty { + + case helpers.String: + + // check if the param is within an enum + if sch.Enum != nil { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(ef) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectQueryParamEnum(params[p], ef, sch)) + } + } + + case helpers.Integer, helpers.Number: + if _, err := strconv.ParseFloat(ef, 64); err != nil { + validationErrors = append(validationErrors, + errors.InvalidQueryParamNumber(params[p], ef, sch)) + break + } + // check if the param is within an enum + if sch.Enum != nil { + matchFound := false + for _, enumVal := range sch.Enum { + if strings.TrimSpace(ef) == fmt.Sprint(enumVal) { + matchFound = true + break + } + } + if !matchFound { + validationErrors = append(validationErrors, + errors.IncorrectQueryParamEnum(params[p], ef, sch)) + } + } + + case helpers.Boolean: + if _, err := strconv.ParseBool(ef); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectQueryParamBool(params[p], ef, sch)) + } + case helpers.Object: + + // check what style of encoding was used and then construct a map[string]interface{} + // and pass that in as encoded JSON. + var encodedObj map[string]interface{} + + switch params[p].Style { + case helpers.DeepObject: + encodedObj = helpers.ConstructParamMapFromDeepObjectEncoding(jk) + case helpers.PipeDelimited: + encodedObj = helpers.ConstructParamMapFromPipeEncoding(jk) + case helpers.SpaceDelimited: + encodedObj = helpers.ConstructParamMapFromSpaceEncoding(jk) + default: + // form encoding is default. + if contentWrapped { + switch contentType { + case helpers.JSONContentType: + // we need to unmarshal the JSON into a map[string]interface{} + encodedParams := make(map[string]interface{}) + encodedObj = make(map[string]interface{}) + if err := json.Unmarshal([]byte(ef), &encodedParams); err != nil { + validationErrors = append(validationErrors, + errors.IncorrectParamEncodingJSON(params[p], ef, sch)) + break skipValues + } + encodedObj[params[p].Name] = encodedParams + } + } else { + encodedObj = helpers.ConstructParamMapFromFormEncodingArray(jk) + } + } + + numErrors := len(validationErrors) + validationErrors = append(validationErrors, + ValidateParameterSchema(sch, encodedObj[params[p].Name].(map[string]interface{}), + ef, + "Query parameter", + "The query parameter", + params[p].Name, + helpers.ParameterValidation, + helpers.ParameterValidationQuery)...) + if len(validationErrors) > numErrors { + // we've already added an error for this, so we can skip the rest of the values + break skipValues + } + + case helpers.Array: + // well we're already in an array, so we need to check the items schema + // to ensure this array items matches the type + // only check if items is a schema, not a boolean + if sch.Items.IsA() { + validationErrors = append(validationErrors, + ValidateQueryArray(sch, params[p], ef, contentWrapped)...) + } + } + } + } + } + + } else { + // if the param is not in the requests, so let's check if this param is an + // object, and if we should use default encoding and explode values. + if params[p].Schema != nil { + sch := params[p].Schema.Schema() + + if sch.Type[0] == helpers.Object && params[p].IsDefaultFormEncoding() { + // if the param is an object, and we're using default encoding, then we need to + // validate the schema. + decoded := helpers.ConstructParamMapFromQueryParamInput(queryParams) + validationErrors = append(validationErrors, + ValidateParameterSchema(sch, + decoded, + "", + "Query array parameter", + "The query parameter (which is an array)", + params[p].Name, + helpers.ParameterValidation, + helpers.ParameterValidationQuery)...) + break doneLooking + } + } + // if there is no match, check if the param is required or not. + if params[p].Required { + validationErrors = append(validationErrors, errors.QueryParameterMissing(params[p])) + } + } + } + } + + v.errors = validationErrors + if len(validationErrors) > 0 { + return false, validationErrors + } + return true, nil } diff --git a/parameters/validation_functions.go b/parameters/validation_functions.go index 888f33d..4e7bcf0 100644 --- a/parameters/validation_functions.go +++ b/parameters/validation_functions.go @@ -4,6 +4,7 @@ package parameters import ( + "fmt" "github.com/pb33f/libopenapi-validator/errors" "github.com/pb33f/libopenapi-validator/helpers" "github.com/pb33f/libopenapi/datamodel/high/base" @@ -129,7 +130,7 @@ func ValidateQueryArray( if itemsSch.Enum != nil { matchFound := false for _, enumVal := range itemsSch.Enum { - if strings.TrimSpace(enumCheck) == enumVal { + if strings.TrimSpace(enumCheck) == fmt.Sprint(enumVal) { matchFound = true break } diff --git a/paths/paths.go b/paths/paths.go index 895461e..5f5185c 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -25,10 +25,12 @@ func FindPath(request *http.Request, document *v3.Document) (*v3.PathItem, []*er var validationErrors []*errors.ValidationError // extract base path from document to check against paths. - basePaths := make([]string, len(document.Servers)) - for i, s := range document.Servers { + var basePaths []string + for _, s := range document.Servers { u, _ := url.Parse(s.URL) - basePaths[i] = u.Path + if u.Path != "" { + basePaths = append(basePaths, u.Path) + } } // strip any base path @@ -200,9 +202,9 @@ pathFound: validationErrors = append(validationErrors, &errors.ValidationError{ ValidationType: helpers.ParameterValidationPath, ValidationSubType: "missing", - Message: fmt.Sprintf("Path '%s' not found", request.URL.Path), - Reason: fmt.Sprintf("The request contains a path of '%s' "+ - "however that path does not exist in the specification", request.URL.Path), + Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path), + Reason: fmt.Sprintf("The %s request contains a path of '%s' "+ + "however that path does not exist in the specification", request.Method, request.URL.Path), SpecLine: -1, SpecCol: -1, }) diff --git a/paths/paths_test.go b/paths/paths_test.go index 69bf315..749ba04 100644 --- a/paths/paths_test.go +++ b/paths/paths_test.go @@ -23,9 +23,9 @@ func TestNewValidator_BadParam(t *testing.T) { _, errs, _ := FindPath(request, &m.Model) - assert.Equal(t, "Path '/pet/doggy' not found", + assert.Equal(t, "GET Path '/pet/doggy' not found", errs[0].Message) - assert.Equal(t, "The request contains a path of '/pet/doggy' however that path does not exist in the specification", + assert.Equal(t, "The GET request contains a path of '/pet/doggy' however that path does not exist in the specification", errs[0].Reason) } @@ -316,7 +316,7 @@ paths: pathItem, errs, _ := FindPath(request, &m.Model) assert.Nil(t, pathItem) assert.NotNil(t, errs) - assert.Equal(t, "Path '/not/here' not found", errs[0].Message) + assert.Equal(t, "HEAD Path '/not/here' not found", errs[0].Message) } @@ -553,3 +553,27 @@ paths: assert.Len(t, errs, 1) } + +func TestNewValidator_DeleteMatch_Error(t *testing.T) { + + spec := `openapi: 3.1.0 +paths: + /pizza/{cakes}: + delete: + operationId: locateBurger + parameters: + - name: cakes + in: path + required: true + schema: + type: string` + + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + + request, _ := http.NewRequest(http.MethodDelete, "https://things.com/pizza/1234", nil) + + _, errs, _ := FindPath(request, &m.Model) + + assert.Len(t, errs, 1) +} diff --git a/requests/validate_body_test.go b/requests/validate_body_test.go index 48c66bd..07ce663 100644 --- a/requests/validate_body_test.go +++ b/requests/validate_body_test.go @@ -98,7 +98,7 @@ paths: assert.False(t, valid) assert.Len(t, errors, 1) - assert.Equal(t, "Path '/I do not exist' not found", errors[0].Message) + assert.Equal(t, "POST Path '/I do not exist' not found", errors[0].Message) } func TestValidateBody_SetPath(t *testing.T) { diff --git a/responses/validate_body_test.go b/responses/validate_body_test.go index d4a4d55..c9621e3 100644 --- a/responses/validate_body_test.go +++ b/responses/validate_body_test.go @@ -131,7 +131,7 @@ paths: assert.False(t, valid) assert.Len(t, errors, 1) - assert.Equal(t, "Path '/I do not exist' not found", errors[0].Message) + assert.Equal(t, "POST Path '/I do not exist' not found", errors[0].Message) } func TestValidateBody_SetPath(t *testing.T) { @@ -193,7 +193,7 @@ paths: assert.False(t, valid) assert.Len(t, errors, 1) - assert.Equal(t, "Path '/I do not exist' not found", errors[0].Message) + assert.Equal(t, "POST Path '/I do not exist' not found", errors[0].Message) } func TestValidateBody_MissingStatusCode(t *testing.T) { diff --git a/schema_validation/locate_schema_property.go b/schema_validation/locate_schema_property.go index 5e8300c..b0c7bbb 100644 --- a/schema_validation/locate_schema_property.go +++ b/schema_validation/locate_schema_property.go @@ -4,20 +4,23 @@ package schema_validation import ( - "github.com/pb33f/libopenapi/utils" - "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" - "gopkg.in/yaml.v3" + "github.com/pb33f/libopenapi/utils" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "gopkg.in/yaml.v3" ) // LocateSchemaPropertyNodeByJSONPath will locate a schema property node by a JSONPath. It converts something like // #/components/schemas/MySchema/properties/MyProperty to something like $.components.schemas.MySchema.properties.MyProperty func LocateSchemaPropertyNodeByJSONPath(doc *yaml.Node, JSONPath string) *yaml.Node { - // first convert the path to something we can use as a lookup, remove the leading slash - _, path := utils.ConvertComponentIdIntoFriendlyPathSearch(JSONPath) - yamlPath, _ := yamlpath.NewPath(path) - locatedNodes, _ := yamlPath.Find(doc) - if len(locatedNodes) > 0 { - return locatedNodes[0] - } - return nil + // first convert the path to something we can use as a lookup, remove the leading slash + _, path := utils.ConvertComponentIdIntoFriendlyPathSearch(JSONPath) + if path == "" { + return nil + } + yamlPath, _ := yamlpath.NewPath(path) + locatedNodes, _ := yamlPath.Find(doc) + if len(locatedNodes) > 0 { + return locatedNodes[0] + } + return nil } diff --git a/schema_validation/locate_schema_property_test.go b/schema_validation/locate_schema_property_test.go new file mode 100644 index 0000000..bce611d --- /dev/null +++ b/schema_validation/locate_schema_property_test.go @@ -0,0 +1,15 @@ +// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package schema_validation + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLocateSchemaPropertyNodeByJSONPath_BadNode(t *testing.T) { + + assert.Nil(t, LocateSchemaPropertyNodeByJSONPath(nil, "")) + +} diff --git a/schema_validation/validate_document.go b/schema_validation/validate_document.go index fa94040..92472f6 100644 --- a/schema_validation/validate_document.go +++ b/schema_validation/validate_document.go @@ -8,38 +8,22 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi-validator/errors" "github.com/pb33f/libopenapi-validator/helpers" - "github.com/pb33f/libopenapi-validator/schema_validation/openapi_schemas" - "github.com/pb33f/libopenapi/utils" "github.com/santhosh-tekuri/jsonschema/v5" + _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" "strings" ) -// ValidateOpenAPIDocument will validate an OpenAPI 3+ document against the OpenAPI 3.0 schema. +// ValidateOpenAPIDocument will validate an OpenAPI document against the OpenAPI 2, 3.0 and 3.1 schemas (depending on version) // It will return true if the document is valid, false if it is not and a slice of ValidationError pointers. -// Swagger / OpenAPI 2.0 documents are not supported by this validator (and they won't be). func ValidateOpenAPIDocument(doc libopenapi.Document) (bool, []*errors.ValidationError) { - // first determine if this is a swagger or an openapi document info := doc.GetSpecInfo() - if info.SpecType == utils.OpenApi2 { - return false, []*errors.ValidationError{{Message: "Swagger / OpenAPI 2.0 is not supported by the validator"}} - } - var loadedSchema string - - // check version of openapi and load schema - switch info.Version { - case "3.1.0", "3.1": - loadedSchema = openapi_schemas.LoadSchema3_1(info.APISchema) - default: - loadedSchema = openapi_schemas.LoadSchema3_0(info.APISchema) - } - + loadedSchema := info.APISchema var validationErrors []*errors.ValidationError - decodedDocument := *info.SpecJSON compiler := jsonschema.NewCompiler() - _ = compiler.AddResource("schema.json", strings.NewReader(string(loadedSchema))) + _ = compiler.AddResource("schema.json", strings.NewReader(loadedSchema)) jsch, _ := compiler.Compile("schema.json") scErrs := jsch.Validate(decodedDocument) diff --git a/schema_validation/validate_document_test.go b/schema_validation/validate_document_test.go index cfa9748..aa83882 100644 --- a/schema_validation/validate_document_test.go +++ b/schema_validation/validate_document_test.go @@ -51,20 +51,3 @@ func TestValidateDocument_Invalid31(t *testing.T) { assert.Len(t, errors[0].SchemaValidationErrors, 4) } - -func TestValidateDocument_InvalidSwagger(t *testing.T) { - - petstore, _ := os.ReadFile("../test_specs/petstorev3.json") - - doc, _ := libopenapi.NewDocument(petstore) - // fake the version so the validator things this is a swagger spec - doc.GetSpecInfo().SpecType = "swagger" - - // validate! - valid, errors := ValidateOpenAPIDocument(doc) - - assert.False(t, valid) - assert.Len(t, errors, 1) - assert.Equal(t, "Swagger / OpenAPI 2.0 is not supported by the validator", errors[0].Message) - -} diff --git a/schema_validation/validate_schema.go b/schema_validation/validate_schema.go index 447dee1..4d00e48 100644 --- a/schema_validation/validate_schema.go +++ b/schema_validation/validate_schema.go @@ -4,14 +4,15 @@ package schema_validation import ( - "encoding/json" - "github.com/pb33f/libopenapi-validator/errors" - "github.com/pb33f/libopenapi-validator/helpers" - "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/utils" - "github.com/santhosh-tekuri/jsonschema/v5" - "gopkg.in/yaml.v3" - "strings" + "encoding/json" + "github.com/pb33f/libopenapi-validator/errors" + "github.com/pb33f/libopenapi-validator/helpers" + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/utils" + "github.com/santhosh-tekuri/jsonschema/v5" + _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" + "gopkg.in/yaml.v3" + "strings" ) // SchemaValidator is an interface that defines the methods for validating a *base.Schema (V3+ Only) object. @@ -22,102 +23,102 @@ import ( // ValidateSchemaBytes accepts a schema object to validate against, and a JSON/YAML blob that is defined as a byte array. type SchemaValidator interface { - // ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string. - ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError) + // ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string. + ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError) - // ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML. - // This is a pre-decoded object that will skip the need to unmarshal a string of JSON/YAML. - ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError) + // ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML. + // This is a pre-decoded object that will skip the need to unmarshal a string of JSON/YAML. + ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError) - // ValidateSchemaBytes accepts a schema object to validate against, and a byte slice containing a schema to - // validate against. - ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError) + // ValidateSchemaBytes accepts a schema object to validate against, and a byte slice containing a schema to + // validate against. + ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError) } type schemaValidator struct{} // NewSchemaValidator will create a new SchemaValidator instance, ready to accept schemas and payloads to validate. func NewSchemaValidator() SchemaValidator { - return &schemaValidator{} + return &schemaValidator{} } func (s *schemaValidator) ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError) { - return validateSchema(schema, []byte(payload), nil) + return validateSchema(schema, []byte(payload), nil) } func (s *schemaValidator) ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError) { - return validateSchema(schema, nil, payload) + return validateSchema(schema, nil, payload) } func (s *schemaValidator) ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError) { - return validateSchema(schema, payload, nil) + return validateSchema(schema, payload, nil) } func validateSchema(schema *base.Schema, payload []byte, decodedObject interface{}) (bool, []*errors.ValidationError) { - var validationErrors []*errors.ValidationError - - // render the schema, to be used for validation - renderedSchema, _ := schema.RenderInline() - jsonSchema, _ := utils.ConvertYAMLtoJSON(renderedSchema) - - if decodedObject == nil { - _ = json.Unmarshal(payload, &decodedObject) - } - compiler := jsonschema.NewCompiler() - _ = compiler.AddResource("schema.json", strings.NewReader(string(jsonSchema))) - jsch, _ := compiler.Compile("schema.json") - - // 4. validate the object against the schema - scErrs := jsch.Validate(decodedObject) - if scErrs != nil { - jk := scErrs.(*jsonschema.ValidationError) - - // flatten the validationErrors - schFlatErrs := jk.BasicOutput().Errors - var schemaValidationErrors []*errors.SchemaValidationFailure - for q := range schFlatErrs { - er := schFlatErrs[q] - if er.KeywordLocation == "" || strings.HasPrefix(er.Error, "doesn't validate with") { - continue // ignore this error, it's useless tbh, utter noise. - } - if er.Error != "" { - - // re-encode the schema. - var renderedNode yaml.Node - _ = yaml.Unmarshal(renderedSchema, &renderedNode) - - // locate the violated property in the schema - located := LocateSchemaPropertyNodeByJSONPath(renderedNode.Content[0], er.KeywordLocation) - violation := &errors.SchemaValidationFailure{ - Reason: er.Error, - Location: er.KeywordLocation, - OriginalError: jk, - } - // if we have a location within the schema, add it to the error - if located != nil { - // location of the violation within the rendered schema. - violation.Line = located.Line - violation.Column = located.Column - } - schemaValidationErrors = append(schemaValidationErrors, violation) - } - } - - // add the error to the list - validationErrors = append(validationErrors, &errors.ValidationError{ - ValidationType: helpers.Schema, - Message: "schema does not pass validation", - Reason: "Schema failed to validated against the contract requirements", - SpecLine: schema.GoLow().Type.KeyNode.Line, - SpecCol: schema.GoLow().Type.KeyNode.Column, - SchemaValidationErrors: schemaValidationErrors, - HowToFix: errors.HowToFixInvalidSchema, - Context: string(renderedSchema), // attach the rendered schema to the error - }) - } - if len(validationErrors) > 0 { - return false, validationErrors - } - return true, nil + var validationErrors []*errors.ValidationError + + // render the schema, to be used for validation + renderedSchema, _ := schema.RenderInline() + jsonSchema, _ := utils.ConvertYAMLtoJSON(renderedSchema) + + if decodedObject == nil { + _ = json.Unmarshal(payload, &decodedObject) + } + compiler := jsonschema.NewCompiler() + _ = compiler.AddResource("schema.json", strings.NewReader(string(jsonSchema))) + jsch, _ := compiler.Compile("schema.json") + + // 4. validate the object against the schema + scErrs := jsch.Validate(decodedObject) + if scErrs != nil { + jk := scErrs.(*jsonschema.ValidationError) + + // flatten the validationErrors + schFlatErrs := jk.BasicOutput().Errors + var schemaValidationErrors []*errors.SchemaValidationFailure + for q := range schFlatErrs { + er := schFlatErrs[q] + if er.KeywordLocation == "" || strings.HasPrefix(er.Error, "doesn't validate with") { + continue // ignore this error, it's useless tbh, utter noise. + } + if er.Error != "" { + + // re-encode the schema. + var renderedNode yaml.Node + _ = yaml.Unmarshal(renderedSchema, &renderedNode) + + // locate the violated property in the schema + located := LocateSchemaPropertyNodeByJSONPath(renderedNode.Content[0], er.KeywordLocation) + violation := &errors.SchemaValidationFailure{ + Reason: er.Error, + Location: er.KeywordLocation, + OriginalError: jk, + } + // if we have a location within the schema, add it to the error + if located != nil { + // location of the violation within the rendered schema. + violation.Line = located.Line + violation.Column = located.Column + } + schemaValidationErrors = append(schemaValidationErrors, violation) + } + } + + // add the error to the list + validationErrors = append(validationErrors, &errors.ValidationError{ + ValidationType: helpers.Schema, + Message: "schema does not pass validation", + Reason: "Schema failed to validate against the contract requirements", + SpecLine: schema.GoLow().Type.KeyNode.Line, + SpecCol: schema.GoLow().Type.KeyNode.Column, + SchemaValidationErrors: schemaValidationErrors, + HowToFix: errors.HowToFixInvalidSchema, + Context: string(renderedSchema), // attach the rendered schema to the error + }) + } + if len(validationErrors) > 0 { + return false, validationErrors + } + return true, nil } diff --git a/validator_examples_test.go b/validator_examples_test.go index c726c49..4b7c92a 100644 --- a/validator_examples_test.go +++ b/validator_examples_test.go @@ -4,155 +4,155 @@ package validator import ( - "encoding/json" - "fmt" - "github.com/pb33f/libopenapi" - "github.com/pb33f/libopenapi-validator/helpers" - "net/http" - "net/http/httptest" - "os" + "encoding/json" + "fmt" + "github.com/pb33f/libopenapi" + "github.com/pb33f/libopenapi-validator/helpers" + "net/http" + "net/http/httptest" + "os" ) func ExampleNewValidator_validateDocument() { - // 1. Load the OpenAPI 3+ spec into a byte array - petstore, err := os.ReadFile("test_specs/invalid_31.yaml") - - if err != nil { - panic(err) - } - - // 2. Create a new OpenAPI document using libopenapi - document, docErrs := libopenapi.NewDocument(petstore) - - if docErrs != nil { - panic(docErrs) - } - - // 3. Create a new validator - docValidator, validatorErrs := NewValidator(document) - - if validatorErrs != nil { - panic(validatorErrs) - } - - // 4. Validate! - valid, validationErrs := docValidator.ValidateDocument() - - if !valid { - for i, e := range validationErrs { - // 5. Handle the error - fmt.Printf("%d: Type: %s, Failure: %s\n", i, e.ValidationType, e.Message) - fmt.Printf("Fix: %s\n\n", e.HowToFix) - } - } - // Output: 0: Type: schema, Failure: Document does not pass validation - //Fix: Ensure that the object being submitted, matches the schema correctly + // 1. Load the OpenAPI 3+ spec into a byte array + petstore, err := os.ReadFile("test_specs/invalid_31.yaml") + + if err != nil { + panic(err) + } + + // 2. Create a new OpenAPI document using libopenapi + document, docErrs := libopenapi.NewDocument(petstore) + + if docErrs != nil { + panic(docErrs) + } + + // 3. Create a new validator + docValidator, validatorErrs := NewValidator(document) + + if validatorErrs != nil { + panic(validatorErrs) + } + + // 4. Validate! + valid, validationErrs := docValidator.ValidateDocument() + + if !valid { + for i, e := range validationErrs { + // 5. Handle the error + fmt.Printf("%d: Type: %s, Failure: %s\n", i, e.ValidationType, e.Message) + fmt.Printf("Fix: %s\n\n", e.HowToFix) + } + } + // Output: 0: Type: schema, Failure: Document does not pass validation + //Fix: Ensure that the object being submitted, matches the schema correctly } func ExampleNewValidator_validateHttpRequest() { - // 1. Load the OpenAPI 3+ spec into a byte array - petstore, err := os.ReadFile("test_specs/petstorev3.json") + // 1. Load the OpenAPI 3+ spec into a byte array + petstore, err := os.ReadFile("test_specs/petstorev3.json") - if err != nil { - panic(err) - } + if err != nil { + panic(err) + } - // 2. Create a new OpenAPI document using libopenapi - document, docErrs := libopenapi.NewDocument(petstore) + // 2. Create a new OpenAPI document using libopenapi + document, docErrs := libopenapi.NewDocument(petstore) - if docErrs != nil { - panic(docErrs) - } + if docErrs != nil { + panic(docErrs) + } - // 3. Create a new validator - docValidator, validatorErrs := NewValidator(document) + // 3. Create a new validator + docValidator, validatorErrs := NewValidator(document) - if validatorErrs != nil { - panic(validatorErrs) - } + if validatorErrs != nil { + panic(validatorErrs) + } - // 4. Create a new *http.Request (normally, this would be where the host application will pass in the request) - request, _ := http.NewRequest(http.MethodGet, "/pet/NotAValidPetId", nil) + // 4. Create a new *http.Request (normally, this would be where the host application will pass in the request) + request, _ := http.NewRequest(http.MethodGet, "/pet/NotAValidPetId", nil) - // 5. Validate! - valid, validationErrs := docValidator.ValidateHttpRequest(request) + // 5. Validate! + valid, validationErrs := docValidator.ValidateHttpRequest(request) - if !valid { - for _, e := range validationErrs { - // 5. Handle the error - fmt.Printf("Type: %s, Failure: %s\n", e.ValidationType, e.Message) - } - } - // Output: Type: path, Failure: Path '/pet/NotAValidPetId' not found + if !valid { + for _, e := range validationErrs { + // 5. Handle the error + fmt.Printf("Type: %s, Failure: %s\n", e.ValidationType, e.Message) + } + } + // Output: Type: path, Failure: GET Path '/pet/NotAValidPetId' not found } func ExampleNewValidator_validateHttpRequestResponse() { - // 1. Load the OpenAPI 3+ spec into a byte array - petstore, err := os.ReadFile("test_specs/petstorev3.json") - - if err != nil { - panic(err) - } - - // 2. Create a new OpenAPI document using libopenapi - document, docErrs := libopenapi.NewDocument(petstore) - - if docErrs != nil { - panic(docErrs) - } - - // 3. Create a new validator - docValidator, validatorErrs := NewValidator(document) - - if validatorErrs != nil { - panic(validatorErrs) - } - - // 6. Create a new *http.Request (normally, this would be where the host application will pass in the request) - request, _ := http.NewRequest(http.MethodGet, "/pet/findByStatus?status=sold", nil) - - // 7. Simulate a request/response, in this case the contract returns a 200 with an array of pets. - // Normally, this would be where the host application would pass in the response. - recorder := httptest.NewRecorder() - handler := func(w http.ResponseWriter, r *http.Request) { - - // set return content type. - w.Header().Set(helpers.ContentTypeHeader, helpers.JSONContentType) - w.WriteHeader(http.StatusOK) - - // create a Pet - body := map[string]interface{}{ - "id": 123, - "name": "cotton", - "category": map[string]interface{}{ - "id": "NotAValidPetId", // this will fail, it should be an integer. - "name": "dogs", - }, - "photoUrls": []string{"https://pb33f.io"}, - } - - // marshal the request body into bytes. - responseBodyBytes, _ := json.Marshal([]interface{}{body}) // operation returns an array of pets - // return the response. - _, _ = w.Write(responseBodyBytes) - } - - // simulate request/response - handler(recorder, request) - - // 7. Validate! - valid, validationErrs := docValidator.ValidateHttpRequestResponse(request, recorder.Result()) - - if !valid { - for _, e := range validationErrs { - // 5. Handle the error - fmt.Printf("Type: %s, Failure: %s\n", e.ValidationType, e.Message) - fmt.Printf("Schema Error: %s, Line: %d, Col: %d\n", - e.SchemaValidationErrors[0].Reason, - e.SchemaValidationErrors[0].Line, - e.SchemaValidationErrors[0].Column) - } - } - // Output: Type: response, Failure: 200 response body for '/pet/findByStatus' failed to validate schema - //Schema Error: expected integer, but got string, Line: 19, Col: 27 + // 1. Load the OpenAPI 3+ spec into a byte array + petstore, err := os.ReadFile("test_specs/petstorev3.json") + + if err != nil { + panic(err) + } + + // 2. Create a new OpenAPI document using libopenapi + document, docErrs := libopenapi.NewDocument(petstore) + + if docErrs != nil { + panic(docErrs) + } + + // 3. Create a new validator + docValidator, validatorErrs := NewValidator(document) + + if validatorErrs != nil { + panic(validatorErrs) + } + + // 6. Create a new *http.Request (normally, this would be where the host application will pass in the request) + request, _ := http.NewRequest(http.MethodGet, "/pet/findByStatus?status=sold", nil) + + // 7. Simulate a request/response, in this case the contract returns a 200 with an array of pets. + // Normally, this would be where the host application would pass in the response. + recorder := httptest.NewRecorder() + handler := func(w http.ResponseWriter, r *http.Request) { + + // set return content type. + w.Header().Set(helpers.ContentTypeHeader, helpers.JSONContentType) + w.WriteHeader(http.StatusOK) + + // create a Pet + body := map[string]interface{}{ + "id": 123, + "name": "cotton", + "category": map[string]interface{}{ + "id": "NotAValidPetId", // this will fail, it should be an integer. + "name": "dogs", + }, + "photoUrls": []string{"https://pb33f.io"}, + } + + // marshal the request body into bytes. + responseBodyBytes, _ := json.Marshal([]interface{}{body}) // operation returns an array of pets + // return the response. + _, _ = w.Write(responseBodyBytes) + } + + // simulate request/response + handler(recorder, request) + + // 7. Validate! + valid, validationErrs := docValidator.ValidateHttpRequestResponse(request, recorder.Result()) + + if !valid { + for _, e := range validationErrs { + // 5. Handle the error + fmt.Printf("Type: %s, Failure: %s\n", e.ValidationType, e.Message) + fmt.Printf("Schema Error: %s, Line: %d, Col: %d\n", + e.SchemaValidationErrors[0].Reason, + e.SchemaValidationErrors[0].Line, + e.SchemaValidationErrors[0].Column) + } + } + // Output: Type: response, Failure: 200 response body for '/pet/findByStatus' failed to validate schema + //Schema Error: expected integer, but got string, Line: 19, Col: 27 } diff --git a/validator_test.go b/validator_test.go index 5c0099f..549b062 100644 --- a/validator_test.go +++ b/validator_test.go @@ -102,7 +102,7 @@ paths: assert.False(t, valid) assert.Len(t, errors, 1) - assert.Equal(t, "Path '/I am a potato man' not found", errors[0].Message) + assert.Equal(t, "POST Path '/I am a potato man' not found", errors[0].Message) } @@ -764,7 +764,7 @@ func TestNewValidator_PetStore_PetGet200_PathNotFound(t *testing.T) { assert.False(t, valid) assert.Len(t, errors, 1) - assert.Equal(t, "Path '/pet/IamNotANumber' not found", errors[0].Message) + assert.Equal(t, "GET Path '/pet/IamNotANumber' not found", errors[0].Message) } func TestNewValidator_PetStore_PetGet200(t *testing.T) {