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 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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
25 changes: 25 additions & 0 deletions example_router_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mux_test
Copy link
Contributor

Choose a reason for hiding this comment

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

We should move this test case in the file: example_route_test.go

Copy link
Author

Choose a reason for hiding this comment

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

modev example


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

// This example demonstrates alias pattern registration on router
func ExampleRouter_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
}
10 changes: 10 additions & 0 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ 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

registeredPatterns map[string]string
}

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

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
40 changes: 40 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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

},
}
for _, test := range tests {
t.Run(test.title, func(t *testing.T) {
Expand Down Expand Up @@ -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}"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Both the test cases look similar to me.

  1. First one:
{
	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,
}
  1. Second:
{
	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,
}

We should add at least one negative test case. What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, seems like this tests was very similar, deleted second test and added negative test case

request: newRequest("GET", "http://localhost/1"),
vars: map[string]string{"id": "1"},
host: "",
path: "/1",
pathTemplate: `/{id:digits}`,
shouldMatch: true,
},
}

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