Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pattern aliases #675

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
* [Registered URLs](#registered-urls)
* [Walking Routes](#walking-routes)
* [Re-using Regular Expressions](#re-using-regular-expressions)
* [Graceful Shutdown](#graceful-shutdown)
* [Middleware](#middleware)
* [Handling CORS Requests](#handling-cors-requests)
Expand Down Expand Up @@ -439,6 +440,32 @@ func main() {
}
```

### Re-using Regular Expressions

There can be a situation when you often need to specify some complex regular expressions inside your paths, e.g. uuid. This can be easily shorthanded:

```go

package main

import (
"net/http"
"github.com/gorilla/mux"
)

func handler(w http.ResponseWriter, r *http.Request) {
return
}

func main() {
r := mux.NewRouter().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}")
r.HandleFunc("/products/{id:uuid}", handler)
r.HandleFunc("/articles/{id:uuid}", handler)
r.HandleFunc("/authors/{id:uuid}", handler)
}
```


### Graceful Shutdown

Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
Expand Down
18 changes: 18 additions & 0 deletions example_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,21 @@ func ExampleRoute_HeadersRegexp_exactMatch() {
// Match: true ["https://example.co"]
// Match: false ["https://example.co.uk"]
}

// This example demonstrates alias pattern registration and usage on router
func ExampleRoute_RegisterPattern() {

r := mux.NewRouter().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}")
route := r.Path("/category/{id:uuid}")

yes, _ := http.NewRequest("GET", "example.co/category/abe193ed-e0bc-4e1b-8e3c-736d5b381b60", nil)
no, _ := http.NewRequest("GET", "example.co/category/42", nil)

mathInfo := &mux.RouteMatch{}
fmt.Printf("Match: %v %q\n", route.Match(yes, mathInfo), yes.URL.Path)
fmt.Printf("Match: %v %q\n", route.Match(no, mathInfo), no.URL.Path)

// Output
// Match: true /category/abe193ed-e0bc-4e1b-8e3c-736d5b381b60
// Match: false /category/42
}
14 changes: 14 additions & 0 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ type routeConf struct {
buildScheme string

buildVarsFunc BuildVarsFunc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, can we add comment here as well?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments

// Map of registered pattern aliases
registeredPatterns map[string]string
}

// returns an effective deep copy of `routeConf`
Expand Down Expand Up @@ -124,6 +127,17 @@ func copyRouteRegexp(r *routeRegexp) *routeRegexp {
return &c
}

// RegisterPattern registers an alias for a frequently repeated regular expression.
//
// It can be used for some popular regular expressions, e.g. uuid, number and etc.
func (r *Router) RegisterPattern(alias string, pattern string) *Router {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add comments here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments

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,
Expand Down
50 changes: 50 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,26 @@ 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add negative test case where shouldMatch is false because of mismatch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added negative test case

},
{
title: "Host route with not matched alias patterns",
route: new(Route).RegisterPattern("pin", "[1-9]{4}").Host("{v-1:pin}.{v-2:pin}.{v-3:pin}"),
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:pin}.{v-2:pin}.{v-3:pin}`,
shouldMatch: false,
},
}
for _, test := range tests {
t.Run(test.title, func(t *testing.T) {
Expand Down Expand Up @@ -449,6 +469,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 not matched regexp alias patterns",
route: new(Route).RegisterPattern("digits", "[0-9]+").Path("/{id:digits}"),
request: newRequest("GET", "http://localhost/letters"),
vars: map[string]string{"id": "1"},
host: "",
path: "/letters",
pathTemplate: `/{id:digits}`,
shouldMatch: false,
},
}

for _, test := range tests {
Expand Down
8 changes: 6 additions & 2 deletions regexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import (
)

type routeRegexpOptions struct {
strictSlash bool
useEncodedPath bool
strictSlash bool
useEncodedPath bool
registeredPatterns map[string]string
}

type regexpType int
Expand Down Expand Up @@ -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)

Expand Down
13 changes: 11 additions & 2 deletions route.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down