Browse Source

Merge pull request #55 from raphael/query-vars

Add ability to capture variables in query strings
pull/57/merge
Kamil Kisiel 12 years ago
parent
commit
2900ff05a4
  1. 36
      mux_test.go
  2. 47
      old_test.go
  3. 37
      regexp.go
  4. 76
      route.go

36
mux_test.go

@ -471,6 +471,42 @@ func TestQueries(t *testing.T) { @@ -471,6 +471,42 @@ func TestQueries(t *testing.T) {
path: "",
shouldMatch: false,
},
{
title: "Queries route with pattern, match",
route: new(Route).Queries("foo", "{v1}"),
request: newRequest("GET", "http://localhost?foo=bar"),
vars: map[string]string{"v1": "bar"},
host: "",
path: "",
shouldMatch: true,
},
{
title: "Queries route with multiple patterns, match",
route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
vars: map[string]string{"v1": "bar", "v2": "ding"},
host: "",
path: "",
shouldMatch: true,
},
{
title: "Queries route with regexp pattern, match",
route: new(Route).Queries("foo", "{v1:[0-9]+}"),
request: newRequest("GET", "http://localhost?foo=10"),
vars: map[string]string{"v1": "10"},
host: "",
path: "",
shouldMatch: true,
},
{
title: "Queries route with regexp pattern, regexp does not match",
route: new(Route).Queries("foo", "{v1:[0-9]+}"),
request: newRequest("GET", "http://localhost?foo=a"),
vars: map[string]string{},
host: "",
path: "",
shouldMatch: false,
},
}
for _, test := range tests {

47
old_test.go

@ -329,35 +329,6 @@ var pathMatcherTests = []pathMatcherTest{ @@ -329,35 +329,6 @@ var pathMatcherTests = []pathMatcherTest{
},
}
type queryMatcherTest struct {
matcher queryMatcher
url string
result bool
}
var queryMatcherTests = []queryMatcherTest{
{
matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}),
url: "http://localhost:8080/?foo=bar&baz=ding",
result: true,
},
{
matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}),
url: "http://localhost:8080/?foo=anything&baz=anything",
result: true,
},
{
matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}),
url: "http://localhost:8080/?foo=bar&baz=ding",
result: false,
},
{
matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}),
url: "http://localhost:8080/?foo=bar&baz=ding",
result: false,
},
}
type schemeMatcherTest struct {
matcher schemeMatcher
url string
@ -519,23 +490,9 @@ func TestPathMatcher(t *testing.T) { @@ -519,23 +490,9 @@ func TestPathMatcher(t *testing.T) {
}
}
func TestQueryMatcher(t *testing.T) {
for _, v := range queryMatcherTests {
request, _ := http.NewRequest("GET", v.url, nil)
var routeMatch RouteMatch
result := v.matcher.Match(request, &routeMatch)
if result != v.result {
if v.result {
t.Errorf("%#v: should match %v.", v.matcher, v.url)
} else {
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
}
}
}
}
func TestSchemeMatcher(t *testing.T) {
for _, v := range queryMatcherTests {
for _, v := range schemeMatcherTests {
request, _ := http.NewRequest("GET", v.url, nil)
var routeMatch RouteMatch
result := v.matcher.Match(request, &routeMatch)
@ -735,7 +692,7 @@ func TestNewRegexp(t *testing.T) { @@ -735,7 +692,7 @@ func TestNewRegexp(t *testing.T) {
}
for pattern, paths := range tests {
p, _ = newRouteRegexp(pattern, false, false, false)
p, _ = newRouteRegexp(pattern, false, false, false, false)
for path, result := range paths {
matches = p.regexp.FindStringSubmatch(path)
if result == nil {

37
regexp.go

@ -14,7 +14,7 @@ import ( @@ -14,7 +14,7 @@ import (
)
// newRouteRegexp parses a route template and returns a routeRegexp,
// used to match a host or path.
// used to match a host, a path or a query string.
//
// It will extract named variables, assemble a regexp to be matched, create
// a "reverse" template to build URLs and compile regexps to validate variable
@ -23,7 +23,7 @@ import ( @@ -23,7 +23,7 @@ import (
// Previously we accepted only Python-like identifiers for variable
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
// name and pattern can't be empty, and names can't contain a colon.
func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) {
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
// Check if it is well-formed.
idxs, errBraces := braceIndices(tpl)
if errBraces != nil {
@ -33,7 +33,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout @@ -33,7 +33,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
template := tpl
// Now let's parse it.
defaultPattern := "[^/]+"
if matchHost {
if matchQuery {
defaultPattern = "[^?]+"
matchPrefix, strictSlash = true, false
} else if matchHost {
defaultPattern = "[^.]+"
matchPrefix, strictSlash = false, false
}
@ -49,6 +52,9 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout @@ -49,6 +52,9 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
varsN := make([]string, len(idxs)/2)
varsR := make([]*regexp.Regexp, len(idxs)/2)
pattern := bytes.NewBufferString("^")
if matchQuery {
pattern = bytes.NewBufferString("")
}
reverse := bytes.NewBufferString("")
var end int
var err error
@ -100,6 +106,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout @@ -100,6 +106,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
return &routeRegexp{
template: template,
matchHost: matchHost,
matchQuery: matchQuery,
strictSlash: strictSlash,
regexp: reg,
reverse: reverse.String(),
@ -113,8 +120,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout @@ -113,8 +120,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
type routeRegexp struct {
// The unmodified template.
template string
// True for host match, false for path match.
// True for host match, false for path or query string match.
matchHost bool
// True for query string match, false for path and host match.
matchQuery bool
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
strictSlash bool
// Expanded regexp.
@ -130,7 +139,11 @@ type routeRegexp struct { @@ -130,7 +139,11 @@ type routeRegexp struct {
// Match matches the regexp against the URL host or path.
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
if !r.matchHost {
return r.regexp.MatchString(req.URL.Path)
if r.matchQuery {
return r.regexp.MatchString(req.URL.RawQuery)
} else {
return r.regexp.MatchString(req.URL.Path)
}
}
return r.regexp.MatchString(getHost(req))
}
@ -196,8 +209,9 @@ func braceIndices(s string) ([]int, error) { @@ -196,8 +209,9 @@ func braceIndices(s string) ([]int, error) {
// routeRegexpGroup groups the route matchers that carry variables.
type routeRegexpGroup struct {
host *routeRegexp
path *routeRegexp
host *routeRegexp
path *routeRegexp
query *routeRegexp
}
// setMatch extracts the variables from the URL once a route matches.
@ -234,6 +248,15 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) @@ -234,6 +248,15 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
}
}
}
// Store query string variables.
if v.query != nil {
queryVars := v.query.regexp.FindStringSubmatch(req.URL.RawQuery)
if queryVars != nil {
for k, v := range v.query.varsN {
m.Vars[v] = queryVars[k+1]
}
}
}
}
// getHost tries its best to return the request host.

76
route.go

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
package mux
import (
"bytes"
"errors"
"fmt"
"net/http"
@ -135,12 +136,12 @@ func (r *Route) addMatcher(m matcher) *Route { @@ -135,12 +136,12 @@ func (r *Route) addMatcher(m matcher) *Route {
}
// addRegexpMatcher adds a host or path matcher and builder to a route.
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error {
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
if r.err != nil {
return r.err
}
r.regexp = r.getRegexpGroup()
if !matchHost {
if !matchHost && !matchQuery {
if len(tpl) == 0 || tpl[0] != '/' {
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
}
@ -148,7 +149,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error @@ -148,7 +149,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
}
}
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash)
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
if err != nil {
return err
}
@ -158,6 +159,11 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error @@ -158,6 +159,11 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error
return err
}
}
if r.regexp.query != nil {
if err = uniqueVars(rr.varsN, r.regexp.query.varsN); err != nil {
return err
}
}
r.regexp.host = rr
} else {
if r.regexp.host != nil {
@ -165,7 +171,21 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error @@ -165,7 +171,21 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error
return err
}
}
r.regexp.path = rr
if matchQuery {
if r.regexp.path != nil {
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
return err
}
}
r.regexp.query = rr
} else {
if r.regexp.query != nil {
if err = uniqueVars(rr.varsN, r.regexp.query.varsN); err != nil {
return err
}
}
r.regexp.path = rr
}
}
r.addMatcher(rr)
return nil
@ -219,7 +239,7 @@ func (r *Route) Headers(pairs ...string) *Route { @@ -219,7 +239,7 @@ func (r *Route) Headers(pairs ...string) *Route {
// Variable names must be unique in a given route. They can be retrieved
// calling mux.Vars(request).
func (r *Route) Host(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, true, false)
r.err = r.addRegexpMatcher(tpl, true, false, false)
return r
}
@ -278,7 +298,7 @@ func (r *Route) Methods(methods ...string) *Route { @@ -278,7 +298,7 @@ func (r *Route) Methods(methods ...string) *Route {
// Variable names must be unique in a given route. They can be retrieved
// calling mux.Vars(request).
func (r *Route) Path(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, false, false)
r.err = r.addRegexpMatcher(tpl, false, false, false)
return r
}
@ -294,35 +314,44 @@ func (r *Route) Path(tpl string) *Route { @@ -294,35 +314,44 @@ func (r *Route) Path(tpl string) *Route {
// Also note that the setting of Router.StrictSlash() has no effect on routes
// with a PathPrefix matcher.
func (r *Route) PathPrefix(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, false, true)
r.err = r.addRegexpMatcher(tpl, false, true, false)
return r
}
// Query ----------------------------------------------------------------------
// queryMatcher matches the request against URL queries.
type queryMatcher map[string]string
func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool {
return matchMap(m, r.URL.Query(), false)
}
// Queries adds a matcher for URL query values.
// It accepts a sequence of key/value pairs. For example:
// It accepts a sequence of key/value pairs. Values may define variables.
// For example:
//
// r := mux.NewRouter()
// r.Queries("foo", "bar", "baz", "ding")
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
//
// The above route will only match if the URL contains the defined queries
// values, e.g.: ?foo=bar&baz=ding.
// values, e.g.: ?foo=bar&id=42.
//
// It the value is an empty string, it will match any value if the key is set.
//
// Variables can define an optional regexp pattern to me matched:
//
// - {name} matches anything until the next slash.
//
// - {name:pattern} matches the given regexp pattern.
func (r *Route) Queries(pairs ...string) *Route {
if r.err == nil {
var queries map[string]string
queries, r.err = mapFromPairs(pairs...)
return r.addMatcher(queryMatcher(queries))
length := len(pairs)
if length%2 != 0 {
r.err = fmt.Errorf(
"mux: number of parameters must be multiple of 2, got %v", pairs)
return nil
}
var buf bytes.Buffer
for i := 0; i < length; i += 2 {
buf.WriteString(fmt.Sprintf("%s=%s&", pairs[i], pairs[i+1]))
}
tpl := strings.TrimRight(buf.String(), "&")
r.err = r.addRegexpMatcher(tpl, false, true, true)
return r
}
@ -498,8 +527,9 @@ func (r *Route) getRegexpGroup() *routeRegexpGroup { @@ -498,8 +527,9 @@ func (r *Route) getRegexpGroup() *routeRegexpGroup {
} else {
// Copy.
r.regexp = &routeRegexpGroup{
host: regexp.host,
path: regexp.path,
host: regexp.host,
path: regexp.path,
query: regexp.query,
}
}
}

Loading…
Cancel
Save