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