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 authored and daveshanley committed Jul 9, 2024
1 parent bc63c2f commit 8c66162
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 109 deletions.
6 changes: 6 additions & 0 deletions errors/validation_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package errors

import (
"fmt"

"github.com/santhosh-tekuri/jsonschema/v5"
)

Expand Down Expand Up @@ -118,3 +119,8 @@ func (v *ValidationError) Error() string {
func (v *ValidationError) IsPathMissingError() bool {
return v.ValidationType == "path" && v.ValidationSubType == "missing"
}

// IsOperationMissingError returns true if the error has a ValidationType of "request" and a ValidationSubType of "missingOperation"
func (v *ValidationError) IsOperationMissingError() bool {
return v.ValidationType == "path" && v.ValidationSubType == "missingOperation"
}
143 changes: 34 additions & 109 deletions paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import (

// 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 @@ -34,7 +32,6 @@ func FindPath(request *http.Request, document *v3.Document) (*v3.PathItem, []*er

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,131 +49,63 @@ pathFound:
segs = segs[1:]
}

// collect path level params
var errs []*errors.ValidationError
var ok bool
ok := comparePaths(segs, reqPathSegments, basePaths)
if !ok {
continue
}
pItem = pathItem
foundPath = path
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
}
}
}
if pItem == nil && len(validationErrors) == 0 {
validationErrors = append(validationErrors, &errors.ValidationError{
if pItem != nil {
validationErrors := []*errors.ValidationError{{
ValidationType: helpers.ParameterValidationPath,
ValidationSubType: "missingOperation",
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
Reason: fmt.Sprintf("The %s method for that path does not exist in the specification",
request.Method),
SpecLine: -1,
SpecCol: -1,
HowToFix: errors.HowToFixPath,
}}
errors.PopulateValidationErrors(validationErrors, request, foundPath)
return pItem, validationErrors, foundPath
}
validationErrors := []*errors.ValidationError{
{
ValidationType: helpers.ParameterValidationPath,
ValidationSubType: "missing",
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
Expand All @@ -186,14 +115,10 @@ pathFound:
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
},
}
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.NotNil(t, pathItem)
assert.NotNil(t, errs)
assert.Equal(t, "PUT Path '/burgers/12345' not found", errs[0].Message)
assert.True(t, errs[0].IsOperationMissingError())
}

func TestNewValidator_GetLiteralMatch(t *testing.T) {

request, _ := http.NewRequest(http.MethodGet, "https://things.com/store/inventory", nil)
Expand Down

0 comments on commit 8c66162

Please sign in to comment.