goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/route.go (about) 1 package goyave 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 "github.com/samber/lo" 9 "goyave.dev/goyave/v5/cors" 10 "goyave.dev/goyave/v5/util/errors" 11 "goyave.dev/goyave/v5/validation" 12 ) 13 14 // Route stores information for route matching and serving and can be 15 // used to generate dynamic URLs/URIs. Routes can, just like routers, 16 // hold Meta information that can be used by generic middleware to 17 // alter their behavior depending on the route being served. 18 type Route struct { 19 name string 20 uri string 21 methods []string 22 parent *Router 23 Meta map[string]any 24 handler Handler 25 middlewareHolder 26 parameterizable 27 } 28 29 var _ routeMatcher = (*Route)(nil) // implements routeMatcher 30 31 // RuleSetFunc function generating a new validation rule set. 32 // This function is called for every validated request. 33 // The returned value is expected to be fresh, not re-used across 34 // multiple requests nor concurrently. 35 type RuleSetFunc func(*Request) validation.RuleSet 36 37 // newRoute create a new route without any settings except its handler. 38 // This is used to generate a fake route for the Method Not Allowed and Not Found handlers. 39 // This route has the core middleware enabled and can be used without a parent router. 40 // Thus, custom status handlers can use language and body. 41 func newRoute(handler Handler, name string) *Route { 42 return &Route{ 43 name: name, 44 handler: handler, 45 Meta: make(map[string]any), 46 middlewareHolder: middlewareHolder{ 47 middleware: nil, 48 }, 49 } 50 } 51 52 func (r *Route) match(method string, match *routeMatch) bool { 53 if params := r.parameterizable.regex.FindStringSubmatch(match.currentPath); params != nil { 54 if r.checkMethod(method) { 55 if len(params) > 1 { 56 match.mergeParams(r.makeParameters(params)) 57 } 58 match.route = r 59 return true 60 } 61 match.err = errMatchMethodNotAllowed 62 return false 63 } 64 65 if match.err == nil { 66 // Don't override error if already set. 67 // Not nil error means it's either already errMatchNotFound 68 // or it's errMatchMethodNotAllowed, implying that a route has 69 // already been matched but with wrong method. 70 match.err = errMatchNotFound 71 } 72 return false 73 } 74 75 func (r *Route) checkMethod(method string) bool { 76 for _, m := range r.methods { 77 if m == method { 78 return true 79 } 80 } 81 return false 82 } 83 84 func (r *Route) makeParameters(match []string) map[string]string { 85 return r.parameterizable.makeParameters(match, r.parameters) 86 } 87 88 // Name set the name of the route. 89 // Panics if a route with the same name already exists. 90 // Returns itself. 91 func (r *Route) Name(name string) *Route { 92 if r.name != "" { 93 panic(errors.NewSkip("route name is already set", 3)) 94 } 95 96 if _, ok := r.parent.namedRoutes[name]; ok { 97 panic(errors.NewSkip(fmt.Errorf("route %q already exists", name), 3)) 98 } 99 100 r.name = name 101 r.parent.namedRoutes[name] = r 102 return r 103 } 104 105 // SetMeta attach a value to this route identified by the given key. 106 // 107 // This value can override a value inherited by the parent routers for this route only. 108 func (r *Route) SetMeta(key string, value any) *Route { 109 r.Meta[key] = value 110 return r 111 } 112 113 // RemoveMeta detach the meta value identified by the given key from this route. 114 // This doesn't remove meta using the same key from the parent routers. 115 func (r *Route) RemoveMeta(key string) *Route { 116 delete(r.Meta, key) 117 return r 118 } 119 120 // LookupMeta value identified by the given key. If not found in this route, 121 // the value is recursively fetched in the parent routers. 122 // 123 // Returns the value and `true` if found in the current route or one of the 124 // parent routers, `nil` and `false` otherwise. 125 func (r *Route) LookupMeta(key string) (any, bool) { 126 val, ok := r.Meta[key] 127 if ok { 128 return val, ok 129 } 130 if r.parent == nil { 131 return nil, false 132 } 133 return r.parent.LookupMeta(key) 134 } 135 136 // ValidateBody adds (or replace) validation rules for the request body. 137 func (r *Route) ValidateBody(validationRules RuleSetFunc) *Route { 138 validationMiddleware := findMiddleware[*validateRequestMiddleware](r.middleware) 139 if validationMiddleware == nil { 140 r.Middleware(&validateRequestMiddleware{BodyRules: validationRules}) 141 } else { 142 validationMiddleware.BodyRules = validationRules 143 } 144 return r 145 } 146 147 // ValidateQuery adds (or replace) validation rules for the request query. 148 func (r *Route) ValidateQuery(validationRules RuleSetFunc) *Route { 149 validationMiddleware := findMiddleware[*validateRequestMiddleware](r.middleware) 150 if validationMiddleware == nil { 151 r.Middleware(&validateRequestMiddleware{QueryRules: validationRules}) 152 } else { 153 validationMiddleware.QueryRules = validationRules 154 } 155 return r 156 } 157 158 // CORS set the CORS options for this route only. 159 // The "OPTIONS" method is added if this route doesn't already support it. 160 // 161 // If the options are not `nil`, the CORS middleware is automatically added globally. 162 // To disable CORS, give `nil` options. The "OPTIONS" method will be removed 163 // if it isn't the only method for this route. 164 func (r *Route) CORS(options *cors.Options) *Route { 165 i := lo.IndexOf(r.methods, http.MethodOptions) 166 if options == nil { 167 r.Meta[MetaCORS] = nil 168 if len(r.methods) > 1 && i != -1 { 169 r.methods = append(r.methods[:i], r.methods[i+1:]...) 170 } 171 return r 172 } 173 r.Meta[MetaCORS] = options 174 if !hasMiddleware[*corsMiddleware](r.parent.globalMiddleware.middleware) { 175 r.parent.GlobalMiddleware(&corsMiddleware{}) 176 } 177 if i == -1 { 178 r.methods = append(r.methods, http.MethodOptions) 179 } 180 return r 181 } 182 183 // Middleware register middleware for this route only. 184 // 185 // Returns itself. 186 func (r *Route) Middleware(middleware ...Middleware) *Route { 187 r.middleware = append(r.middleware, middleware...) 188 for _, m := range middleware { 189 m.Init(r.parent.server) 190 } 191 return r 192 } 193 194 // BuildURL build a full URL pointing to this route. 195 // Panics if the amount of parameters doesn't match the amount of 196 // actual parameters for this route. 197 func (r *Route) BuildURL(parameters ...string) string { 198 return r.parent.server.BaseURL() + r.BuildURI(parameters...) 199 } 200 201 // BuildProxyURL build a full URL pointing to this route using the proxy base URL. 202 // Panics if the amount of parameters doesn't match the amount of 203 // actual parameters for this route. 204 func (r *Route) BuildProxyURL(parameters ...string) string { 205 return r.parent.server.ProxyBaseURL() + r.BuildURI(parameters...) 206 } 207 208 // BuildURI build a full URI pointing to this route. The returned 209 // string doesn't include the protocol and domain. (e.g. "/user/login") 210 // Panics if the amount of parameters doesn't match the amount of 211 // actual parameters for this route. 212 func (r *Route) BuildURI(parameters ...string) string { 213 fullURI, fullParameters := r.GetFullURIAndParameters() 214 215 if len(parameters) != len(fullParameters) { 216 panic(errors.Errorf("BuildURI: route has %d parameters, %d given", len(fullParameters), len(parameters))) 217 } 218 219 var builder strings.Builder 220 builder.Grow(len(fullURI)) 221 222 idxs, _ := r.braceIndices(fullURI) 223 length := len(idxs) 224 end := 0 225 currentParam := 0 226 for i := 0; i < length; i += 2 { 227 raw := fullURI[end:idxs[i]] 228 end = idxs[i+1] 229 builder.WriteString(raw) 230 builder.WriteString(parameters[currentParam]) 231 currentParam++ 232 end++ // Skip closing braces 233 } 234 builder.WriteString(fullURI[end:]) 235 236 return builder.String() 237 } 238 239 // GetName get the name of this route. 240 func (r *Route) GetName() string { 241 return r.name 242 } 243 244 // GetURI get the URI of this route. 245 // The returned URI is relative to the parent router of this route, it is NOT 246 // the full path to this route. 247 // 248 // Note that this URI may contain route parameters in their définition format. 249 // Use the request's URI if you want to see the URI as it was requested by the client. 250 func (r *Route) GetURI() string { 251 return r.uri 252 } 253 254 // GetFullURI get the full URI of this route. 255 // 256 // Note that this URI may contain route parameters in their définition format. 257 // Use the request's URI if you want to see the URI as it was requested by the client. 258 func (r *Route) GetFullURI() string { 259 router := r.parent 260 segments := make([]string, 0, 3) 261 segments = append(segments, r.uri) 262 263 for router != nil { 264 segments = append(segments, router.prefix) 265 router = router.parent 266 } 267 268 // Revert segements 269 for i := len(segments)/2 - 1; i >= 0; i-- { 270 opp := len(segments) - 1 - i 271 segments[i], segments[opp] = segments[opp], segments[i] 272 } 273 274 return strings.Join(segments, "") 275 } 276 277 // GetMethods returns the methods the route matches against. 278 func (r *Route) GetMethods() []string { 279 cpy := make([]string, len(r.methods)) 280 copy(cpy, r.methods) 281 return cpy 282 } 283 284 // GetHandler returns the Handler associated with this route. 285 func (r *Route) GetHandler() Handler { 286 return r.handler 287 } 288 289 // GetParent returns the parent Router of this route. 290 func (r *Route) GetParent() *Router { 291 return r.parent 292 } 293 294 // GetFullURIAndParameters get the full uri and parameters for this route and all its parent routers. 295 func (r *Route) GetFullURIAndParameters() (string, []string) { 296 router := r.parent 297 segments := make([]string, 0, 3) 298 segments = append(segments, r.uri) 299 300 parameters := make([]string, 0, len(r.parameters)) 301 for i := len(r.parameters) - 1; i >= 0; i-- { 302 parameters = append(parameters, r.parameters[i]) 303 } 304 305 for router != nil { 306 segments = append(segments, router.prefix) 307 for i := len(router.parameters) - 1; i >= 0; i-- { 308 parameters = append(parameters, router.parameters[i]) 309 } 310 router = router.parent 311 } 312 313 // Revert segements 314 for i := len(segments)/2 - 1; i >= 0; i-- { 315 opp := len(segments) - 1 - i 316 segments[i], segments[opp] = segments[opp], segments[i] 317 } 318 319 // Revert parameters 320 for i := len(parameters)/2 - 1; i >= 0; i-- { 321 opp := len(parameters) - 1 - i 322 parameters[i], parameters[opp] = parameters[opp], parameters[i] 323 } 324 325 return strings.Join(segments, ""), parameters 326 }