Blob Blame History Raw
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
}