From 27f0c4aaa94798795948cdc26ece94f65a9fa53d Mon Sep 17 00:00:00 2001 From: Bergutov Ruslan Date: Sun, 20 Sep 2020 19:38:41 +0500 Subject: [PATCH] pattern aliases --- mux.go | 10 ++++++++++ mux_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ regexp.go | 8 ++++++-- route.go | 13 +++++++++++-- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/mux.go b/mux.go index 782a34b..855b60a 100644 --- a/mux.go +++ b/mux.go @@ -92,6 +92,8 @@ type routeConf struct { buildScheme string buildVarsFunc BuildVarsFunc + + registeredPatterns map[string]string } // returns an effective deep copy of `routeConf` @@ -122,6 +124,14 @@ func copyRouteRegexp(r *routeRegexp) *routeRegexp { return &c } +func (r *Router) RegisterPattern(alias string, pattern string) *Router { + if r.registeredPatterns == nil { + r.registeredPatterns = map[string]string{} + } + r.registeredPatterns[alias] = pattern + return r +} + // Match attempts to match the given request against the router's registered routes. // // If the request matches a route of this router or one of its subrouters the Route, diff --git a/mux_test.go b/mux_test.go index 2d8d2b3..c4a5f49 100644 --- a/mux_test.go +++ b/mux_test.go @@ -216,6 +216,16 @@ func TestHost(t *testing.T) { hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`, shouldMatch: true, }, + { + title: "Host route with alias patterns", + route: new(Route).RegisterPattern("version", "[a-z]{3}").Host("{v-1:version}.{v-2:version}.{v-3:version}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + hostTemplate: `{v-1:version}.{v-2:version}.{v-3:version}`, + shouldMatch: true, + }, } for _, test := range tests { t.Run(test.title, func(t *testing.T) { @@ -449,6 +459,36 @@ func TestPath(t *testing.T) { pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`, shouldMatch: true, }, + { + title: "Path route with regexp alias patterns", + route: new(Route).RegisterPattern("digits", "[0-9]+").Path("/{id:digits}"), + request: newRequest("GET", "http://localhost/1"), + vars: map[string]string{"id": "1"}, + host: "", + path: "/1", + pathTemplate: `/{id:digits}`, + shouldMatch: true, + }, + { + title: "Path route with regexp alias patterns", + route: new(Route).RegisterPattern("uuid", "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}").Path("/{category:uuid}/{product:uuid}"), + request: newRequest("GET", "http://localhost/dce51145-5cc3-4b54-bfb0-7bdb64a67e4d/a385ddcb-278e-4234-93dd-4d7b0fcb95c1"), + vars: map[string]string{"category": "dce51145-5cc3-4b54-bfb0-7bdb64a67e4d", "product": "a385ddcb-278e-4234-93dd-4d7b0fcb95c1"}, + host: "", + path: "/dce51145-5cc3-4b54-bfb0-7bdb64a67e4d/a385ddcb-278e-4234-93dd-4d7b0fcb95c1", + pathTemplate: `/{category:uuid}/{product:uuid}`, + shouldMatch: true, + }, + { + title: "Path route with regexp alias patterns passed through router", + route: NewRouter().RegisterPattern("digits", "[0-9]+").Path("/{id:digits}"), + request: newRequest("GET", "http://localhost/1"), + vars: map[string]string{"id": "1"}, + host: "", + path: "/1", + pathTemplate: `/{id:digits}`, + shouldMatch: true, + }, } for _, test := range tests { diff --git a/regexp.go b/regexp.go index 0144842..7d40015 100644 --- a/regexp.go +++ b/regexp.go @@ -15,8 +15,9 @@ import ( ) type routeRegexpOptions struct { - strictSlash bool - useEncodedPath bool + strictSlash bool + useEncodedPath bool + registeredPatterns map[string]string } type regexpType int @@ -85,6 +86,9 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro return nil, fmt.Errorf("mux: missing name or pattern in %q", tpl[idxs[i]:end]) } + if registeredPattern, ok := options.registeredPatterns[patt]; ok { + patt = registeredPattern + } // Build the regexp pattern. fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) diff --git a/route.go b/route.go index 750afe5..b27375d 100644 --- a/route.go +++ b/route.go @@ -31,6 +31,14 @@ type Route struct { routeConf } +func (r *Route) RegisterPattern(alias string, pattern string) *Route { + if r.registeredPatterns == nil { + r.registeredPatterns = map[string]string{} + } + r.registeredPatterns[alias] = pattern + return r +} + // SkipClean reports whether path cleaning is enabled for this route via // Router.SkipClean. func (r *Route) SkipClean() bool { @@ -184,8 +192,9 @@ func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { } } rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ - strictSlash: r.strictSlash, - useEncodedPath: r.useEncodedPath, + strictSlash: r.strictSlash, + useEncodedPath: r.useEncodedPath, + registeredPatterns: r.registeredPatterns, }) if err != nil { return err