diff --git a/README.md b/README.md index 9e49b24b..87ada8ec 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,19 @@ url, err := r.Get("article").URL("subdomain", "news", "id", "42") ``` +To find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available: +```go +r := mux.NewRouter() +r.Host("{domain}"). + Path("/{group}/{item_id}"). + Queries("some_data1", "{some_data1}"). + Queries("some_data2", "{some_data2}"). + Name("article") + +// Will print [domain group item_id some_data1 some_data2] +fmt.Println(r.Get("article").GetVarNames()) + +``` ### Walking Routes The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, diff --git a/example_route_vars_test.go b/example_route_vars_test.go new file mode 100644 index 00000000..428c3b6c --- /dev/null +++ b/example_route_vars_test.go @@ -0,0 +1,35 @@ +package mux_test + +import ( + "fmt" + "github.com/gorilla/mux" +) + +// This example demonstrates building a dynamic URL using +// required vars and values retrieve from another source +func ExampleRoute_GetVarNames() { + r := mux.NewRouter() + + route := r.Host("{domain}"). + Path("/{group}/{item_id}"). + Queries("some_data1", "{some_data1}"). + Queries("some_data2_and_3", "{some_data2}.{some_data3}") + + dataSource := func(key string) string { + return "my_value_for_" + key + } + + varNames, _ := route.GetVarNames() + + pairs := make([]string, 0, len(varNames)*2) + + for _, varName := range varNames { + pairs = append(pairs, varName, dataSource(varName)) + } + + url, err := route.URL(pairs...) + if err != nil { + panic(err) + } + fmt.Println(url.String()) +} diff --git a/mux_test.go b/mux_test.go index bd97d33b..5898688b 100644 --- a/mux_test.go +++ b/mux_test.go @@ -2879,6 +2879,40 @@ func TestContextMiddleware(t *testing.T) { r.ServeHTTP(rec, req) } +func TestGetVarNames(t *testing.T) { + r := NewRouter() + + route := r.Host("{domain}"). + Path("/{group}/{item_id}"). + Queries("some_data1", "{some_data1}"). + Queries("some_data2_and_3", "{some_data2}.{some_data3}") + + // Order of vars in the slice is not guaranteed, so just check for existence + expected := map[string]bool{ + "domain": true, + "group": true, + "item_id": true, + "some_data1": true, + "some_data2": true, + "some_data3": true, + } + + varNames, err := route.GetVarNames() + if err != nil { + t.Fatal(err) + } + + if len(varNames) != len(expected) { + t.Fatalf("expected %d names, got %d", len(expected), len(varNames)) + } + + for _, varName := range varNames { + if !expected[varName] { + t.Fatalf("got unexpected %s", varName) + } + } +} + // mapToPairs converts a string map to a slice of string pairs func mapToPairs(m map[string]string) []string { var i int diff --git a/route.go b/route.go index cd85f4b3..e8f11df2 100644 --- a/route.go +++ b/route.go @@ -728,6 +728,25 @@ func (r *Route) GetHostTemplate() (string, error) { return r.regexp.host.template, nil } +// GetVarNames returns the names of all variables added by regexp matchers +// These can be used to know which route variables should be passed into r.URL() +func (r *Route) GetVarNames() ([]string, error) { + if r.err != nil { + return nil, r.err + } + var varNames []string + if r.regexp.host != nil { + varNames = append(varNames, r.regexp.host.varsN...) + } + if r.regexp.path != nil { + varNames = append(varNames, r.regexp.path.varsN...) + } + for _, regx := range r.regexp.queries { + varNames = append(varNames, regx.varsN...) + } + return varNames, nil +} + // prepareVars converts the route variable pairs into a map. If the route has a // BuildVarsFunc, it is invoked. func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {