blob: a591d7354500f7a8bce59f8a544ccbe90e40bb8b [file] [log] [blame]
Jonathan Hartf86817b2018-08-17 10:35:54 -07001// Copyright 2012 The Gorilla Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package mux
6
7import (
8 "errors"
9 "fmt"
10 "net/http"
11 "net/url"
12 "regexp"
13 "strings"
14)
15
16// Route stores information to match a request and build URLs.
17type Route struct {
18 // Parent where the route was registered (a Router).
19 parent parentRoute
20 // Request handler for the route.
21 handler http.Handler
22 // List of matchers.
23 matchers []matcher
24 // Manager for the variables from host and path.
25 regexp *routeRegexpGroup
26 // If true, when the path pattern is "/path/", accessing "/path" will
27 // redirect to the former and vice versa.
28 strictSlash bool
29 // If true, when the path pattern is "/path//to", accessing "/path//to"
30 // will not redirect
31 skipClean bool
32 // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
33 useEncodedPath bool
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080034 // The scheme used when building URLs.
35 buildScheme string
Jonathan Hartf86817b2018-08-17 10:35:54 -070036 // If true, this route never matches: it is only used to build URLs.
37 buildOnly bool
38 // The name used to build URLs.
39 name string
40 // Error resulted from building a route.
41 err error
42
43 buildVarsFunc BuildVarsFunc
44}
45
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080046// SkipClean reports whether path cleaning is enabled for this route via
47// Router.SkipClean.
Jonathan Hartf86817b2018-08-17 10:35:54 -070048func (r *Route) SkipClean() bool {
49 return r.skipClean
50}
51
52// Match matches the route against the request.
53func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
54 if r.buildOnly || r.err != nil {
55 return false
56 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080057
58 var matchErr error
59
Jonathan Hartf86817b2018-08-17 10:35:54 -070060 // Match everything.
61 for _, m := range r.matchers {
62 if matched := m.Match(req, match); !matched {
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080063 if _, ok := m.(methodMatcher); ok {
64 matchErr = ErrMethodMismatch
65 continue
66 }
67 matchErr = nil
Jonathan Hartf86817b2018-08-17 10:35:54 -070068 return false
69 }
70 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080071
72 if matchErr != nil {
73 match.MatchErr = matchErr
74 return false
75 }
76
77 if match.MatchErr == ErrMethodMismatch {
78 // We found a route which matches request method, clear MatchErr
79 match.MatchErr = nil
80 // Then override the mis-matched handler
81 match.Handler = r.handler
82 }
83
Jonathan Hartf86817b2018-08-17 10:35:54 -070084 // Yay, we have a match. Let's collect some info about it.
85 if match.Route == nil {
86 match.Route = r
87 }
88 if match.Handler == nil {
89 match.Handler = r.handler
90 }
91 if match.Vars == nil {
92 match.Vars = make(map[string]string)
93 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -080094
Jonathan Hartf86817b2018-08-17 10:35:54 -070095 // Set variables.
96 if r.regexp != nil {
97 r.regexp.setMatch(req, match, r)
98 }
99 return true
100}
101
102// ----------------------------------------------------------------------------
103// Route attributes
104// ----------------------------------------------------------------------------
105
106// GetError returns an error resulted from building the route, if any.
107func (r *Route) GetError() error {
108 return r.err
109}
110
111// BuildOnly sets the route to never match: it is only used to build URLs.
112func (r *Route) BuildOnly() *Route {
113 r.buildOnly = true
114 return r
115}
116
117// Handler --------------------------------------------------------------------
118
119// Handler sets a handler for the route.
120func (r *Route) Handler(handler http.Handler) *Route {
121 if r.err == nil {
122 r.handler = handler
123 }
124 return r
125}
126
127// HandlerFunc sets a handler function for the route.
128func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
129 return r.Handler(http.HandlerFunc(f))
130}
131
132// GetHandler returns the handler for the route, if any.
133func (r *Route) GetHandler() http.Handler {
134 return r.handler
135}
136
137// Name -----------------------------------------------------------------------
138
139// Name sets the name for the route, used to build URLs.
140// If the name was registered already it will be overwritten.
141func (r *Route) Name(name string) *Route {
142 if r.name != "" {
143 r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
144 r.name, name)
145 }
146 if r.err == nil {
147 r.name = name
148 r.getNamedRoutes()[name] = r
149 }
150 return r
151}
152
153// GetName returns the name for the route, if any.
154func (r *Route) GetName() string {
155 return r.name
156}
157
158// ----------------------------------------------------------------------------
159// Matchers
160// ----------------------------------------------------------------------------
161
162// matcher types try to match a request.
163type matcher interface {
164 Match(*http.Request, *RouteMatch) bool
165}
166
167// addMatcher adds a matcher to the route.
168func (r *Route) addMatcher(m matcher) *Route {
169 if r.err == nil {
170 r.matchers = append(r.matchers, m)
171 }
172 return r
173}
174
175// addRegexpMatcher adds a host or path matcher and builder to a route.
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800176func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
Jonathan Hartf86817b2018-08-17 10:35:54 -0700177 if r.err != nil {
178 return r.err
179 }
180 r.regexp = r.getRegexpGroup()
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800181 if typ == regexpTypePath || typ == regexpTypePrefix {
182 if len(tpl) > 0 && tpl[0] != '/' {
Jonathan Hartf86817b2018-08-17 10:35:54 -0700183 return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
184 }
185 if r.regexp.path != nil {
186 tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
187 }
188 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800189 rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
190 strictSlash: r.strictSlash,
191 useEncodedPath: r.useEncodedPath,
192 })
Jonathan Hartf86817b2018-08-17 10:35:54 -0700193 if err != nil {
194 return err
195 }
196 for _, q := range r.regexp.queries {
197 if err = uniqueVars(rr.varsN, q.varsN); err != nil {
198 return err
199 }
200 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800201 if typ == regexpTypeHost {
Jonathan Hartf86817b2018-08-17 10:35:54 -0700202 if r.regexp.path != nil {
203 if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
204 return err
205 }
206 }
207 r.regexp.host = rr
208 } else {
209 if r.regexp.host != nil {
210 if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
211 return err
212 }
213 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800214 if typ == regexpTypeQuery {
Jonathan Hartf86817b2018-08-17 10:35:54 -0700215 r.regexp.queries = append(r.regexp.queries, rr)
216 } else {
217 r.regexp.path = rr
218 }
219 }
220 r.addMatcher(rr)
221 return nil
222}
223
224// Headers --------------------------------------------------------------------
225
226// headerMatcher matches the request against header values.
227type headerMatcher map[string]string
228
229func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
230 return matchMapWithString(m, r.Header, true)
231}
232
233// Headers adds a matcher for request header values.
234// It accepts a sequence of key/value pairs to be matched. For example:
235//
236// r := mux.NewRouter()
237// r.Headers("Content-Type", "application/json",
238// "X-Requested-With", "XMLHttpRequest")
239//
240// The above route will only match if both request header values match.
241// If the value is an empty string, it will match any value if the key is set.
242func (r *Route) Headers(pairs ...string) *Route {
243 if r.err == nil {
244 var headers map[string]string
245 headers, r.err = mapFromPairsToString(pairs...)
246 return r.addMatcher(headerMatcher(headers))
247 }
248 return r
249}
250
251// headerRegexMatcher matches the request against the route given a regex for the header
252type headerRegexMatcher map[string]*regexp.Regexp
253
254func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
255 return matchMapWithRegex(m, r.Header, true)
256}
257
258// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
259// support. For example:
260//
261// r := mux.NewRouter()
262// r.HeadersRegexp("Content-Type", "application/(text|json)",
263// "X-Requested-With", "XMLHttpRequest")
264//
265// The above route will only match if both the request header matches both regular expressions.
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800266// If the value is an empty string, it will match any value if the key is set.
267// Use the start and end of string anchors (^ and $) to match an exact value.
Jonathan Hartf86817b2018-08-17 10:35:54 -0700268func (r *Route) HeadersRegexp(pairs ...string) *Route {
269 if r.err == nil {
270 var headers map[string]*regexp.Regexp
271 headers, r.err = mapFromPairsToRegex(pairs...)
272 return r.addMatcher(headerRegexMatcher(headers))
273 }
274 return r
275}
276
277// Host -----------------------------------------------------------------------
278
279// Host adds a matcher for the URL host.
280// It accepts a template with zero or more URL variables enclosed by {}.
281// Variables can define an optional regexp pattern to be matched:
282//
283// - {name} matches anything until the next dot.
284//
285// - {name:pattern} matches the given regexp pattern.
286//
287// For example:
288//
289// r := mux.NewRouter()
290// r.Host("www.example.com")
291// r.Host("{subdomain}.domain.com")
292// r.Host("{subdomain:[a-z]+}.domain.com")
293//
294// Variable names must be unique in a given route. They can be retrieved
295// calling mux.Vars(request).
296func (r *Route) Host(tpl string) *Route {
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800297 r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
Jonathan Hartf86817b2018-08-17 10:35:54 -0700298 return r
299}
300
301// MatcherFunc ----------------------------------------------------------------
302
303// MatcherFunc is the function signature used by custom matchers.
304type MatcherFunc func(*http.Request, *RouteMatch) bool
305
306// Match returns the match for a given request.
307func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
308 return m(r, match)
309}
310
311// MatcherFunc adds a custom function to be used as request matcher.
312func (r *Route) MatcherFunc(f MatcherFunc) *Route {
313 return r.addMatcher(f)
314}
315
316// Methods --------------------------------------------------------------------
317
318// methodMatcher matches the request against HTTP methods.
319type methodMatcher []string
320
321func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
322 return matchInArray(m, r.Method)
323}
324
325// Methods adds a matcher for HTTP methods.
326// It accepts a sequence of one or more methods to be matched, e.g.:
327// "GET", "POST", "PUT".
328func (r *Route) Methods(methods ...string) *Route {
329 for k, v := range methods {
330 methods[k] = strings.ToUpper(v)
331 }
332 return r.addMatcher(methodMatcher(methods))
333}
334
335// Path -----------------------------------------------------------------------
336
337// Path adds a matcher for the URL path.
338// It accepts a template with zero or more URL variables enclosed by {}. The
339// template must start with a "/".
340// Variables can define an optional regexp pattern to be matched:
341//
342// - {name} matches anything until the next slash.
343//
344// - {name:pattern} matches the given regexp pattern.
345//
346// For example:
347//
348// r := mux.NewRouter()
349// r.Path("/products/").Handler(ProductsHandler)
350// r.Path("/products/{key}").Handler(ProductsHandler)
351// r.Path("/articles/{category}/{id:[0-9]+}").
352// Handler(ArticleHandler)
353//
354// Variable names must be unique in a given route. They can be retrieved
355// calling mux.Vars(request).
356func (r *Route) Path(tpl string) *Route {
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800357 r.err = r.addRegexpMatcher(tpl, regexpTypePath)
Jonathan Hartf86817b2018-08-17 10:35:54 -0700358 return r
359}
360
361// PathPrefix -----------------------------------------------------------------
362
363// PathPrefix adds a matcher for the URL path prefix. This matches if the given
364// template is a prefix of the full URL path. See Route.Path() for details on
365// the tpl argument.
366//
367// Note that it does not treat slashes specially ("/foobar/" will be matched by
368// the prefix "/foo") so you may want to use a trailing slash here.
369//
370// Also note that the setting of Router.StrictSlash() has no effect on routes
371// with a PathPrefix matcher.
372func (r *Route) PathPrefix(tpl string) *Route {
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800373 r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
Jonathan Hartf86817b2018-08-17 10:35:54 -0700374 return r
375}
376
377// Query ----------------------------------------------------------------------
378
379// Queries adds a matcher for URL query values.
380// It accepts a sequence of key/value pairs. Values may define variables.
381// For example:
382//
383// r := mux.NewRouter()
384// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
385//
386// The above route will only match if the URL contains the defined queries
387// values, e.g.: ?foo=bar&id=42.
388//
389// It the value is an empty string, it will match any value if the key is set.
390//
391// Variables can define an optional regexp pattern to be matched:
392//
393// - {name} matches anything until the next slash.
394//
395// - {name:pattern} matches the given regexp pattern.
396func (r *Route) Queries(pairs ...string) *Route {
397 length := len(pairs)
398 if length%2 != 0 {
399 r.err = fmt.Errorf(
400 "mux: number of parameters must be multiple of 2, got %v", pairs)
401 return nil
402 }
403 for i := 0; i < length; i += 2 {
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800404 if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
Jonathan Hartf86817b2018-08-17 10:35:54 -0700405 return r
406 }
407 }
408
409 return r
410}
411
412// Schemes --------------------------------------------------------------------
413
414// schemeMatcher matches the request against URL schemes.
415type schemeMatcher []string
416
417func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
418 return matchInArray(m, r.URL.Scheme)
419}
420
421// Schemes adds a matcher for URL schemes.
422// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
423func (r *Route) Schemes(schemes ...string) *Route {
424 for k, v := range schemes {
425 schemes[k] = strings.ToLower(v)
426 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800427 if r.buildScheme == "" && len(schemes) > 0 {
428 r.buildScheme = schemes[0]
429 }
Jonathan Hartf86817b2018-08-17 10:35:54 -0700430 return r.addMatcher(schemeMatcher(schemes))
431}
432
433// BuildVarsFunc --------------------------------------------------------------
434
435// BuildVarsFunc is the function signature used by custom build variable
436// functions (which can modify route variables before a route's URL is built).
437type BuildVarsFunc func(map[string]string) map[string]string
438
439// BuildVarsFunc adds a custom function to be used to modify build variables
440// before a route's URL is built.
441func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
442 r.buildVarsFunc = f
443 return r
444}
445
446// Subrouter ------------------------------------------------------------------
447
448// Subrouter creates a subrouter for the route.
449//
450// It will test the inner routes only if the parent route matched. For example:
451//
452// r := mux.NewRouter()
453// s := r.Host("www.example.com").Subrouter()
454// s.HandleFunc("/products/", ProductsHandler)
455// s.HandleFunc("/products/{key}", ProductHandler)
456// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
457//
458// Here, the routes registered in the subrouter won't be tested if the host
459// doesn't match.
460func (r *Route) Subrouter() *Router {
461 router := &Router{parent: r, strictSlash: r.strictSlash}
462 r.addMatcher(router)
463 return router
464}
465
466// ----------------------------------------------------------------------------
467// URL building
468// ----------------------------------------------------------------------------
469
470// URL builds a URL for the route.
471//
472// It accepts a sequence of key/value pairs for the route variables. For
473// example, given this route:
474//
475// r := mux.NewRouter()
476// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
477// Name("article")
478//
479// ...a URL for it can be built using:
480//
481// url, err := r.Get("article").URL("category", "technology", "id", "42")
482//
483// ...which will return an url.URL with the following path:
484//
485// "/articles/technology/42"
486//
487// This also works for host variables:
488//
489// r := mux.NewRouter()
490// r.Host("{subdomain}.domain.com").
491// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
492// Name("article")
493//
494// // url.String() will be "http://news.domain.com/articles/technology/42"
495// url, err := r.Get("article").URL("subdomain", "news",
496// "category", "technology",
497// "id", "42")
498//
499// All variables defined in the route are required, and their values must
500// conform to the corresponding patterns.
501func (r *Route) URL(pairs ...string) (*url.URL, error) {
502 if r.err != nil {
503 return nil, r.err
504 }
505 if r.regexp == nil {
506 return nil, errors.New("mux: route doesn't have a host or path")
507 }
508 values, err := r.prepareVars(pairs...)
509 if err != nil {
510 return nil, err
511 }
512 var scheme, host, path string
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800513 queries := make([]string, 0, len(r.regexp.queries))
Jonathan Hartf86817b2018-08-17 10:35:54 -0700514 if r.regexp.host != nil {
Jonathan Hartf86817b2018-08-17 10:35:54 -0700515 if host, err = r.regexp.host.url(values); err != nil {
516 return nil, err
517 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800518 scheme = "http"
519 if s := r.getBuildScheme(); s != "" {
520 scheme = s
521 }
Jonathan Hartf86817b2018-08-17 10:35:54 -0700522 }
523 if r.regexp.path != nil {
524 if path, err = r.regexp.path.url(values); err != nil {
525 return nil, err
526 }
527 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800528 for _, q := range r.regexp.queries {
529 var query string
530 if query, err = q.url(values); err != nil {
531 return nil, err
532 }
533 queries = append(queries, query)
534 }
Jonathan Hartf86817b2018-08-17 10:35:54 -0700535 return &url.URL{
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800536 Scheme: scheme,
537 Host: host,
538 Path: path,
539 RawQuery: strings.Join(queries, "&"),
Jonathan Hartf86817b2018-08-17 10:35:54 -0700540 }, nil
541}
542
543// URLHost builds the host part of the URL for a route. See Route.URL().
544//
545// The route must have a host defined.
546func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
547 if r.err != nil {
548 return nil, r.err
549 }
550 if r.regexp == nil || r.regexp.host == nil {
551 return nil, errors.New("mux: route doesn't have a host")
552 }
553 values, err := r.prepareVars(pairs...)
554 if err != nil {
555 return nil, err
556 }
557 host, err := r.regexp.host.url(values)
558 if err != nil {
559 return nil, err
560 }
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800561 u := &url.URL{
Jonathan Hartf86817b2018-08-17 10:35:54 -0700562 Scheme: "http",
563 Host: host,
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800564 }
565 if s := r.getBuildScheme(); s != "" {
566 u.Scheme = s
567 }
568 return u, nil
Jonathan Hartf86817b2018-08-17 10:35:54 -0700569}
570
571// URLPath builds the path part of the URL for a route. See Route.URL().
572//
573// The route must have a path defined.
574func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
575 if r.err != nil {
576 return nil, r.err
577 }
578 if r.regexp == nil || r.regexp.path == nil {
579 return nil, errors.New("mux: route doesn't have a path")
580 }
581 values, err := r.prepareVars(pairs...)
582 if err != nil {
583 return nil, err
584 }
585 path, err := r.regexp.path.url(values)
586 if err != nil {
587 return nil, err
588 }
589 return &url.URL{
590 Path: path,
591 }, nil
592}
593
594// GetPathTemplate returns the template used to build the
595// route match.
596// This is useful for building simple REST API documentation and for instrumentation
597// against third-party services.
598// An error will be returned if the route does not define a path.
599func (r *Route) GetPathTemplate() (string, error) {
600 if r.err != nil {
601 return "", r.err
602 }
603 if r.regexp == nil || r.regexp.path == nil {
604 return "", errors.New("mux: route doesn't have a path")
605 }
606 return r.regexp.path.template, nil
607}
608
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800609// GetPathRegexp returns the expanded regular expression used to match route path.
610// This is useful for building simple REST API documentation and for instrumentation
611// against third-party services.
612// An error will be returned if the route does not define a path.
613func (r *Route) GetPathRegexp() (string, error) {
614 if r.err != nil {
615 return "", r.err
616 }
617 if r.regexp == nil || r.regexp.path == nil {
618 return "", errors.New("mux: route does not have a path")
619 }
620 return r.regexp.path.regexp.String(), nil
621}
622
623// GetQueriesRegexp returns the expanded regular expressions used to match the
624// route queries.
625// This is useful for building simple REST API documentation and for instrumentation
626// against third-party services.
627// An error will be returned if the route does not have queries.
628func (r *Route) GetQueriesRegexp() ([]string, error) {
629 if r.err != nil {
630 return nil, r.err
631 }
632 if r.regexp == nil || r.regexp.queries == nil {
633 return nil, errors.New("mux: route doesn't have queries")
634 }
635 var queries []string
636 for _, query := range r.regexp.queries {
637 queries = append(queries, query.regexp.String())
638 }
639 return queries, nil
640}
641
642// GetQueriesTemplates returns the templates used to build the
643// query matching.
644// This is useful for building simple REST API documentation and for instrumentation
645// against third-party services.
646// An error will be returned if the route does not define queries.
647func (r *Route) GetQueriesTemplates() ([]string, error) {
648 if r.err != nil {
649 return nil, r.err
650 }
651 if r.regexp == nil || r.regexp.queries == nil {
652 return nil, errors.New("mux: route doesn't have queries")
653 }
654 var queries []string
655 for _, query := range r.regexp.queries {
656 queries = append(queries, query.template)
657 }
658 return queries, nil
659}
660
661// GetMethods returns the methods the route matches against
662// This is useful for building simple REST API documentation and for instrumentation
663// against third-party services.
664// An error will be returned if route does not have methods.
665func (r *Route) GetMethods() ([]string, error) {
666 if r.err != nil {
667 return nil, r.err
668 }
669 for _, m := range r.matchers {
670 if methods, ok := m.(methodMatcher); ok {
671 return []string(methods), nil
672 }
673 }
674 return nil, errors.New("mux: route doesn't have methods")
675}
676
Jonathan Hartf86817b2018-08-17 10:35:54 -0700677// GetHostTemplate returns the template used to build the
678// route match.
679// This is useful for building simple REST API documentation and for instrumentation
680// against third-party services.
681// An error will be returned if the route does not define a host.
682func (r *Route) GetHostTemplate() (string, error) {
683 if r.err != nil {
684 return "", r.err
685 }
686 if r.regexp == nil || r.regexp.host == nil {
687 return "", errors.New("mux: route doesn't have a host")
688 }
689 return r.regexp.host.template, nil
690}
691
692// prepareVars converts the route variable pairs into a map. If the route has a
693// BuildVarsFunc, it is invoked.
694func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
695 m, err := mapFromPairsToString(pairs...)
696 if err != nil {
697 return nil, err
698 }
699 return r.buildVars(m), nil
700}
701
702func (r *Route) buildVars(m map[string]string) map[string]string {
703 if r.parent != nil {
704 m = r.parent.buildVars(m)
705 }
706 if r.buildVarsFunc != nil {
707 m = r.buildVarsFunc(m)
708 }
709 return m
710}
711
712// ----------------------------------------------------------------------------
713// parentRoute
714// ----------------------------------------------------------------------------
715
716// parentRoute allows routes to know about parent host and path definitions.
717type parentRoute interface {
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800718 getBuildScheme() string
Jonathan Hartf86817b2018-08-17 10:35:54 -0700719 getNamedRoutes() map[string]*Route
720 getRegexpGroup() *routeRegexpGroup
721 buildVars(map[string]string) map[string]string
722}
723
Matteo Scandolo1f49bf52018-11-20 13:56:45 -0800724func (r *Route) getBuildScheme() string {
725 if r.buildScheme != "" {
726 return r.buildScheme
727 }
728 if r.parent != nil {
729 return r.parent.getBuildScheme()
730 }
731 return ""
732}
733
Jonathan Hartf86817b2018-08-17 10:35:54 -0700734// getNamedRoutes returns the map where named routes are registered.
735func (r *Route) getNamedRoutes() map[string]*Route {
736 if r.parent == nil {
737 // During tests router is not always set.
738 r.parent = NewRouter()
739 }
740 return r.parent.getNamedRoutes()
741}
742
743// getRegexpGroup returns regexp definitions from this route.
744func (r *Route) getRegexpGroup() *routeRegexpGroup {
745 if r.regexp == nil {
746 if r.parent == nil {
747 // During tests router is not always set.
748 r.parent = NewRouter()
749 }
750 regexp := r.parent.getRegexpGroup()
751 if regexp == nil {
752 r.regexp = new(routeRegexpGroup)
753 } else {
754 // Copy.
755 r.regexp = &routeRegexpGroup{
756 host: regexp.host,
757 path: regexp.path,
758 queries: regexp.queries,
759 }
760 }
761 }
762 return r.regexp
763}