diff --git a/examples/gno.land/p/demo/mux/request.gno b/examples/gno.land/p/demo/mux/request.gno index f7996fe40fe..7b5b74da91b 100644 --- a/examples/gno.land/p/demo/mux/request.gno +++ b/examples/gno.land/p/demo/mux/request.gno @@ -4,7 +4,15 @@ import "strings" // Request represents an incoming request. type Request struct { - Path string + // Path is request path name. + // + // Note: use RawPath to obtain a raw path with query string. + Path string + + // RawPath contains a whole request path, including query string. + RawPath string + + // HandlerPath is handler rule that matches a request. HandlerPath string } diff --git a/examples/gno.land/p/demo/mux/router.gno b/examples/gno.land/p/demo/mux/router.gno index a2efb3a4ebf..fe6bf70abdf 100644 --- a/examples/gno.land/p/demo/mux/router.gno +++ b/examples/gno.land/p/demo/mux/router.gno @@ -18,7 +18,8 @@ func NewRouter() *Router { // Render renders the output for the given path using the registered route handler. func (r *Router) Render(reqPath string) string { - reqParts := strings.Split(reqPath, "/") + clearPath := stripQueryString(reqPath) + reqParts := strings.Split(clearPath, "/") for _, route := range r.routes { patParts := strings.Split(route.Pattern, "/") @@ -45,7 +46,8 @@ func (r *Router) Render(reqPath string) string { } if match { req := &Request{ - Path: reqPath, + Path: clearPath, + RawPath: reqPath, HandlerPath: route.Pattern, } res := &ResponseWriter{} @@ -66,3 +68,12 @@ func (r *Router) HandleFunc(pattern string, fn HandlerFunc) { route := Handler{Pattern: pattern, Fn: fn} r.routes = append(r.routes, route) } + +func stripQueryString(reqPath string) string { + i := strings.Index(reqPath, "?") + if i == -1 { + return reqPath + } + + return reqPath[:i] +} diff --git a/examples/gno.land/p/demo/mux/router_test.gno b/examples/gno.land/p/demo/mux/router_test.gno index 13fd5b97955..cc6aad62146 100644 --- a/examples/gno.land/p/demo/mux/router_test.gno +++ b/examples/gno.land/p/demo/mux/router_test.gno @@ -1,34 +1,85 @@ package mux -import "testing" +import ( + "testing" -func TestRouter_Render(t *testing.T) { - // Define handlers and route configuration - router := NewRouter() - router.HandleFunc("hello/{name}", func(res *ResponseWriter, req *Request) { - name := req.GetVar("name") - if name != "" { - res.Write("Hello, " + name + "!") - } else { - res.Write("Hello, world!") - } - }) - router.HandleFunc("hi", func(res *ResponseWriter, req *Request) { - res.Write("Hi, earth!") - }) + "gno.land/p/demo/uassert" +) +func TestRouter_Render(t *testing.T) { cases := []struct { + label string path string expectedOutput string + setupHandler func(t *testing.T, r *Router) }{ - {"hello/Alice", "Hello, Alice!"}, - {"hi", "Hi, earth!"}, - {"hello/Bob", "Hello, Bob!"}, + { + label: "route with named parameter", + path: "hello/Alice", + expectedOutput: "Hello, Alice!", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hello/{name}", func(rw *ResponseWriter, req *Request) { + name := req.GetVar("name") + uassert.Equal(t, "Alice", name) + rw.Write("Hello, " + name + "!") + }) + }, + }, + { + label: "static route", + path: "hi", + expectedOutput: "Hi, earth!", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hi", func(rw *ResponseWriter, req *Request) { + uassert.Equal(t, req.Path, "hi") + rw.Write("Hi, earth!") + }) + }, + }, + { + label: "route with named parameter and query string", + path: "hello/foo/bar?foo=bar&baz", + expectedOutput: "foo bar", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hello/{key}/{val}", func(rw *ResponseWriter, req *Request) { + key := req.GetVar("key") + val := req.GetVar("val") + uassert.Equal(t, "foo", key) + uassert.Equal(t, "bar", val) + uassert.Equal(t, "hello/foo/bar?foo=bar&baz", req.RawPath) + uassert.Equal(t, "hello/foo/bar", req.Path) + rw.Write(key + " " + val) + }) + }, + }, + { + // TODO: finalize how router should behave with double slash in path. + label: "double slash in nested route", + path: "a/foo//", + expectedOutput: "test foo", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("a/{key}", func(rw *ResponseWriter, req *Request) { + // Assert not called + uassert.False(t, true, "unexpected handler called") + }) + + r.HandleFunc("a/{key}/{val}/", func(rw *ResponseWriter, req *Request) { + key := req.GetVar("key") + val := req.GetVar("val") + uassert.Equal(t, key, "foo") + uassert.Empty(t, val) + rw.Write("test " + key) + }) + }, + }, + // TODO: {"hello", "Hello, world!"}, // TODO: hello/, /hello, hello//Alice, hello/Alice/, hello/Alice/Bob, etc } for _, tt := range cases { - t.Run(tt.path, func(t *testing.T) { + t.Run(tt.label, func(t *testing.T) { + router := NewRouter() + tt.setupHandler(t, router) output := router.Render(tt.path) if output != tt.expectedOutput { t.Errorf("Expected output %q, but got %q", tt.expectedOutput, output) diff --git a/gnovm/cmd/gno/download_deps_test.go b/gnovm/cmd/gno/download_deps_test.go index 3ccfdb0055e..0828e9b2245 100644 --- a/gnovm/cmd/gno/download_deps_test.go +++ b/gnovm/cmd/gno/download_deps_test.go @@ -60,7 +60,7 @@ func TestDownloadDeps(t *testing.T) { }, }, }, - requirements: []string{"avl", "blog", "ufmt", "mux"}, + requirements: []string{"avl", "blog", "diff", "uassert", "ufmt", "mux"}, ioErrContains: []string{ "gno: downloading gno.land/p/demo/blog", "gno: downloading gno.land/p/demo/avl",