Skip to content

Commit

Permalink
feat(examples/mux): support query string in path (gnolang#3281)
Browse files Browse the repository at this point in the history
This PR adds support for request URLs with query strings for
`p/demo/mux` package.

Previously, `mux.Router` would fail to find a correct handler if request
URL contains query string.

```go
r := mux.NewRouter()
r.HandleFunc("hello", func (rw *mux.ResponseWriter, req *mux.Request) {
  ...
})

reqUrl := "hello?foo=bar"
r.Render(reqUrl) // Fails
```

This PR fixes this behavior and introduces a new `mux.Request.RawPath`
field which contains a raw request path including query string.

The `RawPath` field is designed to be used for packages like
`p/demo/avl/pager` to extract query params from a string.\
The `Path` field, as before, contains just path segment of request,
without query strings.

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
</details>


CC @moul @thehowl @jeronimoalbi

---------

Co-authored-by: Leon Hudak <[email protected]>
Co-authored-by: Morgan <[email protected]>
  • Loading branch information
3 people authored and Villaquiranm committed Dec 9, 2024
1 parent 2135a2b commit 4425503
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 23 deletions.
10 changes: 9 additions & 1 deletion examples/gno.land/p/demo/mux/request.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
15 changes: 13 additions & 2 deletions examples/gno.land/p/demo/mux/router.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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, "/")
Expand All @@ -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{}
Expand All @@ -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]
}
89 changes: 70 additions & 19 deletions examples/gno.land/p/demo/mux/router_test.gno
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion gnovm/cmd/gno/download_deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 4425503

Please sign in to comment.