goyave.dev/goyave/v4@v4.4.11/route.go (about) 1 package goyave 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 "goyave.dev/goyave/v4/validation" 9 ) 10 11 // Route stores information for matching and serving. 12 type Route struct { 13 name string 14 uri string 15 methods []string 16 parent *Router 17 handler Handler 18 validationRules *validation.Rules 19 middlewareHolder 20 parameterizable 21 } 22 23 var _ routeMatcher = (*Route)(nil) // implements routeMatcher 24 25 // newRoute create a new route without any settings except its handler. 26 // This is used to generate a fake route for the Method Not Allowed and Not Found handlers. 27 // This route has the core middleware enabled and can be used without a parent router. 28 // Thus, custom status handlers can use language and body. 29 func newRoute(handler Handler) *Route { 30 return &Route{ 31 handler: handler, 32 middlewareHolder: middlewareHolder{ 33 middleware: []Middleware{recoveryMiddleware, parseRequestMiddleware, languageMiddleware}, 34 }, 35 } 36 } 37 38 func (r *Route) match(req *http.Request, match *routeMatch) bool { 39 if params := r.parameterizable.regex.FindStringSubmatch(match.currentPath); params != nil { 40 if r.checkMethod(req.Method) { 41 if len(params) > 1 { 42 match.mergeParams(r.makeParameters(params)) 43 } 44 match.route = r 45 return true 46 } 47 match.err = errMatchMethodNotAllowed 48 return false 49 } 50 51 if match.err == nil { 52 // Don't override error if already set. 53 // Not nil error means it's either already errMatchNotFound 54 // or it's errMatchMethodNotAllowed, implying that a route has 55 // already been matched but with wrong method. 56 match.err = errMatchNotFound 57 } 58 return false 59 } 60 61 func (r *Route) checkMethod(method string) bool { 62 for _, m := range r.methods { 63 if m == method { 64 return true 65 } 66 } 67 return false 68 } 69 70 func (r *Route) makeParameters(match []string) map[string]string { 71 return r.parameterizable.makeParameters(match, r.parameters) 72 } 73 74 // Name set the name of the route. 75 // Panics if a route with the same name already exists. 76 // Returns itself. 77 func (r *Route) Name(name string) *Route { 78 if r.name != "" { 79 panic(fmt.Errorf("Route name is already set")) 80 } 81 82 if _, ok := r.parent.namedRoutes[name]; ok { 83 panic(fmt.Errorf("Route %q already exists", name)) 84 } 85 86 r.name = name 87 r.parent.namedRoutes[name] = r 88 return r 89 } 90 91 // Validate adds validation rules to this route. If the user-submitted data 92 // doesn't pass validation, the user will receive an error and messages explaining 93 // what is wrong. 94 // 95 // Returns itself. 96 func (r *Route) Validate(validationRules validation.Ruler) *Route { 97 r.validationRules = validationRules.AsRules() 98 return r 99 } 100 101 // Middleware register middleware for this route only. 102 // 103 // Returns itself. 104 func (r *Route) Middleware(middleware ...Middleware) *Route { 105 r.middleware = middleware 106 return r 107 } 108 109 // BuildURL build a full URL pointing to this route. 110 // Panics if the amount of parameters doesn't match the amount of 111 // actual parameters for this route. 112 func (r *Route) BuildURL(parameters ...string) string { 113 return BaseURL() + r.BuildURI(parameters...) 114 } 115 116 // BuildURI build a full URI pointing to this route. The returned 117 // string doesn't include the protocol and domain. (e.g. "/user/login") 118 // Panics if the amount of parameters doesn't match the amount of 119 // actual parameters for this route. 120 func (r *Route) BuildURI(parameters ...string) string { 121 fullURI, fullParameters := r.GetFullURIAndParameters() 122 123 if len(parameters) != len(fullParameters) { 124 panic(fmt.Errorf("BuildURI: route has %d parameters, %d given", len(fullParameters), len(parameters))) 125 } 126 127 var builder strings.Builder 128 builder.Grow(len(fullURI)) 129 130 idxs, _ := r.braceIndices(fullURI) 131 length := len(idxs) 132 end := 0 133 currentParam := 0 134 for i := 0; i < length; i += 2 { 135 raw := fullURI[end:idxs[i]] 136 end = idxs[i+1] 137 builder.WriteString(raw) 138 builder.WriteString(parameters[currentParam]) 139 currentParam++ 140 end++ // Skip closing braces 141 } 142 builder.WriteString(fullURI[end:]) 143 144 return builder.String() 145 } 146 147 // GetName get the name of this route. 148 func (r *Route) GetName() string { 149 return r.name 150 } 151 152 // GetURI get the URI of this route. 153 // The returned URI is relative to the parent router of this route, it is NOT 154 // the full path to this route. 155 // 156 // Note that this URI may contain route parameters in their définition format. 157 // Use the request's URI if you want to see the URI as it was requested by the client. 158 func (r *Route) GetURI() string { 159 return r.uri 160 } 161 162 // GetFullURI get the full URI of this route. 163 // 164 // Note that this URI may contain route parameters in their définition format. 165 // Use the request's URI if you want to see the URI as it was requested by the client. 166 func (r *Route) GetFullURI() string { 167 router := r.parent 168 segments := make([]string, 0, 3) 169 segments = append(segments, r.uri) 170 171 for router != nil { 172 segments = append(segments, router.prefix) 173 router = router.parent 174 } 175 176 // Revert segements 177 for i := len(segments)/2 - 1; i >= 0; i-- { 178 opp := len(segments) - 1 - i 179 segments[i], segments[opp] = segments[opp], segments[i] 180 } 181 182 return strings.Join(segments, "") 183 } 184 185 // GetMethods returns the methods the route matches against. 186 func (r *Route) GetMethods() []string { 187 cpy := make([]string, len(r.methods)) 188 copy(cpy, r.methods) 189 return cpy 190 } 191 192 // GetHandler returns the Handler associated with this route. 193 func (r *Route) GetHandler() Handler { 194 return r.handler 195 } 196 197 // GetValidationRules returns the validation rules associated with this route. 198 func (r *Route) GetValidationRules() *validation.Rules { 199 return r.validationRules 200 } 201 202 // GetFullURIAndParameters get the full uri and parameters for this route and all its parent routers. 203 func (r *Route) GetFullURIAndParameters() (string, []string) { 204 router := r.parent 205 segments := make([]string, 0, 3) 206 segments = append(segments, r.uri) 207 208 parameters := make([]string, 0, len(r.parameters)) 209 for i := len(r.parameters) - 1; i >= 0; i-- { 210 parameters = append(parameters, r.parameters[i]) 211 } 212 213 for router != nil { 214 segments = append(segments, router.prefix) 215 for i := len(router.parameters) - 1; i >= 0; i-- { 216 parameters = append(parameters, router.parameters[i]) 217 } 218 router = router.parent 219 } 220 221 // Revert segements 222 for i := len(segments)/2 - 1; i >= 0; i-- { 223 opp := len(segments) - 1 - i 224 segments[i], segments[opp] = segments[opp], segments[i] 225 } 226 227 // Revert parameters 228 for i := len(parameters)/2 - 1; i >= 0; i-- { 229 opp := len(parameters) - 1 - i 230 parameters[i], parameters[opp] = parameters[opp], parameters[i] 231 } 232 233 return strings.Join(segments, ""), parameters 234 }