Skip to content

Commit

Permalink
FindPath reports path and operation not found
Browse files Browse the repository at this point in the history
  • Loading branch information
emilien-puget committed Apr 28, 2024
1 parent 5e5b19b commit 6f0968a
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 121 deletions.
15 changes: 15 additions & 0 deletions errors/request_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions errors/validation_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package errors

import (
"fmt"

"github.com/pb33f/libopenapi-validator/helpers"
"github.com/santhosh-tekuri/jsonschema/v5"
)

Expand Down Expand Up @@ -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
}
143 changes: 22 additions & 121 deletions paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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()
Expand All @@ -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

Check warning on line 90 in paths/paths.go

View check run for this annotation

Codecov / codecov/patch

paths/paths.go#L87-L90

Added lines #L87 - L90 were not covered by tests
}
}
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 {
Expand Down
22 changes: 22 additions & 0 deletions paths/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 6f0968a

Please sign in to comment.