about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFranck Cuny <franck@lumberjaph.net>2013-04-21 11:38:48 -0700
committerFranck Cuny <franck@lumberjaph.net>2013-04-21 11:38:48 -0700
commit67e3e5b2bf87bc77de70063347337c4787f0df48 (patch)
treea5b9a8be9c15f1aa62b430b27132f2a8ef75ff9e
parentAdd very simple test to validate routes. (diff)
downloadpath-router-67e3e5b2bf87bc77de70063347337c4787f0df48.tar.gz
Add support for optional parameter in a path.
If a component in the path starts with "?:", it means that it's
optional.  We then store the lenght of the components without those
optional parameters.
-rw-r--r--route.go24
-rw-r--r--router_test.go26
2 files changed, 43 insertions, 7 deletions
diff --git a/route.go b/route.go
index 1950307..d84d026 100644
--- a/route.go
+++ b/route.go
@@ -14,6 +14,7 @@ type Route struct {
 	RequiredNamedComponents map[string]bool
 	OptionalNamedComponents map[string]bool
 	Length                  int
+	LengthWithoutOptional int
 }
 
 type Match struct {
@@ -24,6 +25,7 @@ type Match struct {
 }
 
 var componentIsVariable = regexp.MustCompile("^:")
+var componentsIsOptional = regexp.MustCompile("^?:")
 var namedComponentsRegex = regexp.MustCompile("^:(.*)$")
 
 func (self *Route) Match(request Request) *Match {
@@ -40,13 +42,16 @@ func (self *Route) Match(request Request) *Match {
 
 	components := strings.Split(request.Request.URL.Path, "/")
 
-	if len(components) < self.Length {
+	if len(components) < self.LengthWithoutOptional || len(components) > self.Length {
 		return nil
 	}
 
 	mapping := map[string]string{}
 
 	for i, c := range self.Components {
+		if componentsIsOptional.MatchString(c) {
+			break
+		}
 		p := components[i]
 
 		if componentIsVariable.MatchString(c) == true {
@@ -83,26 +88,31 @@ func MakeRoute(path string, method string, code func(*Request) (Response, error)
 		}
 	}
 
-	namedComponents := getNamedComponents(components)
+	reqComponents, optComponents := getNamedComponents(components)
 	exec := fn{method: code}
 
 	route := Route{
 		Path:                    path,
 		Executors:               exec,
 		Components:              components,
-		RequiredNamedComponents: namedComponents,
+		RequiredNamedComponents: reqComponents,
+		OptionalNamedComponents: optComponents,
 		Length:                  len(components),
+		LengthWithoutOptional:   len(components) - len(optComponents),
 	}
 
 	return route
 }
 
-func getNamedComponents(components []string) map[string]bool {
-	namedComponents := map[string]bool{}
+func getNamedComponents(components []string) (map[string]bool, map[string]bool) {
+	reqComponents := map[string]bool{}
+	optComponents := map[string]bool{}
 	for _, c := range components {
 		if namedComponentsRegex.MatchString(c) == true {
-			namedComponents[c] = true
+			reqComponents[c] = true
+		} else if componentsIsOptional.MatchString(c) == true {
+			optComponents[c] = true
 		}
 	}
-	return namedComponents
+	return reqComponents, optComponents
 }
diff --git a/router_test.go b/router_test.go
index 5b85aa8..28895b9 100644
--- a/router_test.go
+++ b/router_test.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 	"net/url"
 	"testing"
+	"fmt"
 )
 
 func testRoute(req *Request) (Response, error) {
@@ -41,6 +42,31 @@ func TestMatch(t *testing.T) {
 		m := router.Match(req)
 		if m == nil {
 			t.Fatal()
+		}else{
+			fmt.Println(fmt.Sprintf("%s match for %s", p.Path, m.Path))
+		}
+	}
+}
+
+func TestMatchOptional(t *testing.T) {
+	router := BuildDispatcher()
+	router.AddRoute("/blog/?:year", "GET", testRoute)
+	router.AddRoute("/blog/:year/?:month", "GET", testRoute)
+
+	pathToTests := []url.URL{
+		url.URL{Path: "blog"},
+		url.URL{Path: "/blog"},
+		url.URL{Path: "blog/2013"},
+		url.URL{Path: "/blog/2013/21"},
+	}
+	for _, p := range pathToTests {
+		r := http.Request{URL: &p, Method: "GET"}
+		req := Request{&r}
+		m := router.Match(req)
+		if m == nil {
+			t.Fatal()
+		}else{
+			fmt.Println(fmt.Sprintf("%s match for %s", p.Path, m.Path))
 		}
 	}
 }