1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
// Package legacy implements a router.
//
// It differs from the gorilla/mux router:
// * it provides granular errors: "path not found", "method not allowed", "variable missing from path"
// * it does not handle matching routes with extensions (e.g. /books/{id}.json)
// * it handles path patterns with a different syntax (e.g. /params/{x}/{y}/{z.*})
package legacy
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
"github.com/getkin/kin-openapi/routers/legacy/pathpattern"
)
// Routers maps a HTTP request to a Router.
type Routers []*Router
// FindRoute extracts the route and parameters of an http.Request
func (rs Routers) FindRoute(req *http.Request) (routers.Router, *routers.Route, map[string]string, error) {
for _, router := range rs {
// Skip routers that have DO NOT have servers
if len(router.doc.Servers) == 0 {
continue
}
route, pathParams, err := router.FindRoute(req)
if err == nil {
return router, route, pathParams, nil
}
}
for _, router := range rs {
// Skip routers that DO have servers
if len(router.doc.Servers) > 0 {
continue
}
route, pathParams, err := router.FindRoute(req)
if err == nil {
return router, route, pathParams, nil
}
}
return nil, nil, nil, &routers.RouteError{
Reason: "none of the routers match",
}
}
// Router maps a HTTP request to an OpenAPI operation.
type Router struct {
doc *openapi3.T
pathNode *pathpattern.Node
}
// NewRouter creates a new router.
//
// If the given OpenAPIv3 document has servers, router will use them.
// All operations of the document will be added to the router.
func NewRouter(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error) {
if err := doc.Validate(context.Background(), opts...); err != nil {
return nil, fmt.Errorf("validating OpenAPI failed: %w", err)
}
router := &Router{doc: doc}
root := router.node()
for path, pathItem := range doc.Paths.Map() {
for method, operation := range pathItem.Operations() {
method = strings.ToUpper(method)
if err := root.Add(method+" "+path, &routers.Route{
Spec: doc,
Path: path,
PathItem: pathItem,
Method: method,
Operation: operation,
}, nil); err != nil {
return nil, err
}
}
}
return router, nil
}
// AddRoute adds a route in the router.
func (router *Router) AddRoute(route *routers.Route) error {
method := route.Method
if method == "" {
return errors.New("route is missing method")
}
method = strings.ToUpper(method)
path := route.Path
if path == "" {
return errors.New("route is missing path")
}
return router.node().Add(method+" "+path, router, nil)
}
func (router *Router) node() *pathpattern.Node {
root := router.pathNode
if root == nil {
root = &pathpattern.Node{}
router.pathNode = root
}
return root
}
// FindRoute extracts the route and parameters of an http.Request
func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) {
method, url := req.Method, req.URL
doc := router.doc
// Get server
servers := doc.Servers
var server *openapi3.Server
var remainingPath string
var pathParams map[string]string
if len(servers) == 0 {
remainingPath = url.Path
} else {
var paramValues []string
server, paramValues, remainingPath = servers.MatchURL(url)
if server == nil {
return nil, nil, &routers.RouteError{
Reason: routers.ErrPathNotFound.Error(),
}
}
pathParams = make(map[string]string)
paramNames, err := server.ParameterNames()
if err != nil {
return nil, nil, err
}
for i, value := range paramValues {
name := paramNames[i]
pathParams[name] = value
}
}
// Get PathItem
root := router.node()
var route *routers.Route
node, paramValues := root.Match(method + " " + remainingPath)
if node != nil {
route, _ = node.Value.(*routers.Route)
}
if route == nil {
pathItem := doc.Paths.Value(remainingPath)
if pathItem == nil {
return nil, nil, &routers.RouteError{Reason: routers.ErrPathNotFound.Error()}
}
if pathItem.GetOperation(method) == nil {
return nil, nil, &routers.RouteError{Reason: routers.ErrMethodNotAllowed.Error()}
}
}
if pathParams == nil {
pathParams = make(map[string]string, len(paramValues))
}
paramKeys := node.VariableNames
for i, value := range paramValues {
key := strings.TrimSuffix(paramKeys[i], "*")
pathParams[key] = value
}
return route, pathParams, nil
}
|