diff --git a/errors/request_errors.go b/errors/request_errors.go index 0ad3b92..578261e 100644 --- a/errors/request_errors.go +++ b/errors/request_errors.go @@ -36,6 +36,21 @@ func RequestContentTypeNotFound(op *v3.Operation, request *http.Request, specPat } } +func PathNotFound(request *http.Request) *ValidationError { + return &ValidationError{ + ValidationType: helpers.ParameterValidationPath, + ValidationSubType: "missing", + 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, + HowToFix: HowToFixPath, + } +} + + func OperationNotFound(pathItem *v3.PathItem, request *http.Request, method string, specPath string) *ValidationError { return &ValidationError{ ValidationType: helpers.RequestValidation, diff --git a/errors/validation_error.go b/errors/validation_error.go index e7ca406..3c3543c 100644 --- a/errors/validation_error.go +++ b/errors/validation_error.go @@ -5,6 +5,8 @@ package errors import ( "fmt" + + "github.com/pb33f/libopenapi-validator/helpers" "github.com/santhosh-tekuri/jsonschema/v5" ) @@ -118,3 +120,8 @@ func (v *ValidationError) Error() string { func (v *ValidationError) IsPathMissingError() bool { return v.ValidationType == "path" && v.ValidationSubType == "missing" } + +// IsOperationNotFoundError returns true if the error has a ValidationType of "request" and a ValidationSubType of "missingOperation" +func (v *ValidationError) IsOperationNotFoundError() bool { + return v.ValidationType == helpers.RequestValidation && v.ValidationSubType == helpers.RequestMissingOperation +} diff --git a/paths/paths.go b/paths/paths.go index 2976195..d7b00ea 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -11,19 +11,16 @@ import ( "strings" "github.com/pb33f/libopenapi-validator/errors" - "github.com/pb33f/libopenapi-validator/helpers" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" "github.com/pb33f/libopenapi/orderedmap" ) // FindPath will find the path in the document that matches the request path. If a successful match was found, then // the first return value will be a pointer to the PathItem. The second return value will contain any validation errors -// that were picked up when locating the path. Number/Integer validation is performed in any path parameters in the request. +// that were picked up when locating the path. // The third return value will be the path that was found in the document, as it pertains to the contract, so all path // parameters will not have been replaced with their values from the request - allowing model lookups. func FindPath(request *http.Request, document *v3.Document) (*v3.PathItem, []*errors.ValidationError, string) { - var validationErrors []*errors.ValidationError - basePaths := getBasePaths(document) stripped := StripRequestPath(request, document) @@ -32,9 +29,6 @@ func FindPath(request *http.Request, document *v3.Document) (*v3.PathItem, []*er reqPathSegments = reqPathSegments[1:] } - var pItem *v3.PathItem - var foundPath string -pathFound: for pair := orderedmap.First(document.Paths.PathItems); pair != nil; pair = pair.Next() { path := pair.Key() pathItem := pair.Value() @@ -52,148 +46,55 @@ pathFound: segs = segs[1:] } - // collect path level params - var errs []*errors.ValidationError - var ok bool + ok := comparePaths(segs, reqPathSegments, basePaths) + if !ok { + continue + } + switch request.Method { case http.MethodGet: if pathItem.Get != nil { - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodPost: if pathItem.Post != nil { - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodPut: if pathItem.Put != nil { - // check for a literal match - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodDelete: if pathItem.Delete != nil { - // check for a literal match - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodOptions: if pathItem.Options != nil { - // check for a literal match - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodHead: if pathItem.Head != nil { - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodPatch: if pathItem.Patch != nil { - // check for a literal match - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } case http.MethodTrace: if pathItem.Trace != nil { - if checkPathAgainstBase(request.URL.Path, path, basePaths) { - pItem = pathItem - foundPath = path - break pathFound - } - if ok = comparePaths(segs, reqPathSegments, basePaths); ok { - pItem = pathItem - foundPath = path - validationErrors = errs - break pathFound - } + return pathItem, nil, path } + default: + validationErrors := []*errors.ValidationError{errors.OperationNotFound(pathItem, request, request.Method, path)} + errors.PopulateValidationErrors(validationErrors, request, path) + return nil, validationErrors, path } } - if pItem == nil && len(validationErrors) == 0 { - validationErrors = append(validationErrors, &errors.ValidationError{ - ValidationType: helpers.ParameterValidationPath, - ValidationSubType: "missing", - 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, or the %s method for that path does not exist in the specification", - request.Method, request.URL.Path, request.Method), - SpecLine: -1, - SpecCol: -1, - HowToFix: errors.HowToFixPath, - }) - - errors.PopulateValidationErrors(validationErrors, request, foundPath) - return pItem, validationErrors, foundPath - } else { - errors.PopulateValidationErrors(validationErrors, request, foundPath) - return pItem, validationErrors, foundPath + validationErrors := []*errors.ValidationError{ + errors.PathNotFound(request), } + errors.PopulateValidationErrors(validationErrors, request, "") + return nil, validationErrors, "" } func getBasePaths(document *v3.Document) []string { diff --git a/paths/paths_test.go b/paths/paths_test.go index aff1451..e3ed14b 100644 --- a/paths/paths_test.go +++ b/paths/paths_test.go @@ -314,9 +314,31 @@ paths: assert.Nil(t, pathItem) assert.NotNil(t, errs) assert.Equal(t, "HEAD Path '/not/here' not found", errs[0].Message) + assert.True(t, errs[0].IsPathMissingError()) } +func TestNewValidator_FindOperationMissing(t *testing.T) { + + spec := `openapi: 3.1.0 +paths: + /burgers/{burgerId}: + trace: + operationId: locateBurger +` + + doc, _ := libopenapi.NewDocument([]byte(spec)) + m, _ := doc.BuildV3Model() + + request, _ := http.NewRequest(http.MethodPut, "https://things.com/burgers/12345", nil) + + pathItem, errs, _ := FindPath(request, &m.Model) + assert.Nil(t, pathItem) + assert.NotNil(t, errs) + assert.Equal(t, "PUT Path '/burgers/12345' not found", errs[0].Message) + assert.True(t, errs[0].IsPathMissingError()) +} + func TestNewValidator_GetLiteralMatch(t *testing.T) { request, _ := http.NewRequest(http.MethodGet, "https://things.com/store/inventory", nil)