Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FindPath reports path and operation not found #72

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading