127 lines
2.9 KiB
Go
127 lines
2.9 KiB
Go
package openapi3
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Paths is specified by OpenAPI/Swagger standard version 3.0.
|
|
type Paths map[string]*PathItem
|
|
|
|
func (paths Paths) Validate(c context.Context) error {
|
|
normalizedPaths := make(map[string]string)
|
|
for path, pathItem := range paths {
|
|
if path == "" || path[0] != '/' {
|
|
return fmt.Errorf("path %q does not start with a forward slash (/)", path)
|
|
}
|
|
|
|
if pathItem == nil {
|
|
paths[path] = &PathItem{}
|
|
pathItem = paths[path]
|
|
}
|
|
|
|
normalizedPath, pathParamsCount := normalizeTemplatedPath(path)
|
|
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
|
|
return fmt.Errorf("conflicting paths %q and %q", path, oldPath)
|
|
}
|
|
normalizedPaths[path] = path
|
|
|
|
var globalCount uint
|
|
for _, parameterRef := range pathItem.Parameters {
|
|
if parameterRef != nil {
|
|
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
|
|
globalCount++
|
|
}
|
|
}
|
|
}
|
|
for method, operation := range pathItem.Operations() {
|
|
var count uint
|
|
for _, parameterRef := range operation.Parameters {
|
|
if parameterRef != nil {
|
|
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
if count+globalCount != pathParamsCount {
|
|
return fmt.Errorf("operation %s %s must define exactly all path parameters", method, path)
|
|
}
|
|
}
|
|
|
|
if err := pathItem.Validate(c); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Find returns a path that matches the key.
|
|
//
|
|
// The method ignores differences in template variable names (except possible "*" suffix).
|
|
//
|
|
// For example:
|
|
//
|
|
// paths := openapi3.Paths {
|
|
// "/person/{personName}": &openapi3.PathItem{},
|
|
// }
|
|
// pathItem := path.Find("/person/{name}")
|
|
//
|
|
// would return the correct path item.
|
|
func (paths Paths) Find(key string) *PathItem {
|
|
// Try directly access the map
|
|
pathItem := paths[key]
|
|
if pathItem != nil {
|
|
return pathItem
|
|
}
|
|
|
|
normalizedPath, expected := normalizeTemplatedPath(key)
|
|
for path, pathItem := range paths {
|
|
pathNormalized, got := normalizeTemplatedPath(path)
|
|
if got == expected && pathNormalized == normalizedPath {
|
|
return pathItem
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeTemplatedPath(path string) (string, uint) {
|
|
if strings.IndexByte(path, '{') < 0 {
|
|
return path, 0
|
|
}
|
|
|
|
var buf strings.Builder
|
|
buf.Grow(len(path))
|
|
|
|
var (
|
|
cc rune
|
|
count uint
|
|
isVariable bool
|
|
)
|
|
for i, c := range path {
|
|
if isVariable {
|
|
if c == '}' {
|
|
// End path variables
|
|
// First append possible '*' before this character
|
|
// The character '}' will be appended
|
|
if i > 0 && cc == '*' {
|
|
buf.WriteRune(cc)
|
|
}
|
|
isVariable = false
|
|
} else {
|
|
// Skip this character
|
|
continue
|
|
}
|
|
} else if c == '{' {
|
|
// Begin path variable
|
|
// The character '{' will be appended
|
|
isVariable = true
|
|
count++
|
|
}
|
|
|
|
// Append the character
|
|
buf.WriteRune(c)
|
|
cc = c
|
|
}
|
|
return buf.String(), count
|
|
}
|