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