Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mux): support query string in path #3281

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/gno.land/p/demo/mux/gno.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module gno.land/p/demo/mux

require gno.land/p/demo/uassert v0.0.0-latest
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
Loading