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) } 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 }