Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
67 changes: 67 additions & 0 deletions examples/gno.land/p/demo/mux/query.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package mux

import "strings"

type QueryValues map[string][]string

/*
Query() returns a full map of key-listValue from request
to get a list of params by key, use: req.Query()["key"]
to get single param by key, use: req.Query().Get("key")
to get request path use GetPath()
*/

func (r *Request) QueryFull() (QueryValues, string) {
urlQueries := QueryValues{}
pathQuery := ""
// checking if there is query in r.Path
if strings.Contains(r.Path, "?") {
// get baseUrl and queryString
rParts := strings.SplitN(r.Path, "?", 2)
if len(rParts) == 1 {
// if this is not a `query string` query, then returns full path as queryPath
return urlQueries, rParts[0]
}
baseUrl := rParts[0]
queryString := rParts[1]

// find the target path
baseUrlParts := strings.Split(baseUrl, "/")
rTargetPath := baseUrlParts[len(baseUrlParts)-1]
pathQuery = rTargetPath

// process the queryString
// find the first param index
if strings.Contains(queryString, "=") {
for _, keyValue := range strings.Split(queryString, "&") {
parts := strings.SplitN(keyValue, "=", 2)
if len(parts) != 2 {
return urlQueries, rParts[0]
}
key, value := parts[0], parts[1]
urlQueries[key] = append(urlQueries[key], value)
}
}
}
return urlQueries, pathQuery
}

// get the query by key
// if there is more than one query, returns the very first param
func (qs QueryValues) Get(key string) string {
listMatching, ok := qs[key]
if ok {
return listMatching[0]
}
return ""
}

func (r *Request) Query() QueryValues {
uQuery, _ := r.QueryFull()
return uQuery
}

func (r *Request) GetQueryPath() string {
_, pQuery := r.QueryFull()
return pQuery
}
101 changes: 101 additions & 0 deletions examples/gno.land/p/demo/mux/query_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package mux

import (
"fmt"
"testing"
)

func TestQuery_Get(t *testing.T) {
cases := []struct {
name string
reqPath string
key string
expectedOutput string
}{
// get by key
{"Get_query_key", "api/v1/?name=testname&age=12", "name", "testname"},
{"Get_query_repeat", "api/v1/user?name=name1&name=name2&name=name3", "name", "name1"},
{"Get_query_with_pre_path", "api/v1/unprocesspath?user=thinhnx&age=12", "user", "thinhnx"},
{"Get_query_empty", "api/v1/?name=testname&age=12", "loc", ""},
{"Get_query_with_slash", "api/v1/?name=testname&endpoint=testdomain.com/v1/with/slash/", "endpoint", "testdomain.com/v1/with/slash/"},
{"Get_query_with_extra_slash", "api/v1/unprocesspath?user=thinhnx&age=12&addr=12/34/NewYork//", "addr", "12/34/NewYork//"},
}

for _, tt := range cases {
name := fmt.Sprintf("%s-%s", tt.name, tt.reqPath)
t.Run(name, func(t *testing.T) {
req := &Request{
HandlerPath: "",
Path: tt.reqPath,
}
output := req.Query().Get(tt.key)
if output != tt.expectedOutput {
t.Errorf("Expected %q, but got %q", tt.expectedOutput, output)
}
})
}
}

func TestQuery_QueryList(t *testing.T) {
cases := []struct {
name string
reqPath string
key string
expectedOutput []string
}{
// get all queries
{"Get_name_list", "api/v1/user?name=name0&age=12&name=name1&age=30", "name", []string{"name0", "name1"}},
{"Get_age_list", "api/v1/user?name=name0&age=12&name=name1&age=30", "age", []string{"12", "30"}},
{"Get_loc_list", "api/v1/user?name=name0&age=12&name=name1&age=30&loc=HN", "loc", []string{"HN"}},
{"Get_empty_list", "api/v1/user?name=name0&age=12&name=name1&age=30&loc=HN", "addr", []string{}},
}

for _, tt := range cases {
name := fmt.Sprintf("%s-%s", tt.name, tt.reqPath)
t.Run(name, func(t *testing.T) {
req := &Request{
HandlerPath: "",
Path: tt.reqPath,
}
// simple check for length of expected and output list
output := req.Query()[tt.key]
if len(output) != len(tt.expectedOutput) {
t.Errorf("Expected %q, but got %q", tt.expectedOutput, output)
}
// check the corresponding elements
listOutput := req.Query()[tt.key]
for i, ttExpt := range tt.expectedOutput {
if ttExpt != listOutput[i] {
t.Errorf("Expected %q, but got %q", ttExpt, listOutput[i])
}
}
})
}
}

func TestQuery_GetPath(t *testing.T) {
cases := []struct {
name string
reqPath string
expectedOutput string
}{
// get all queries
{"Get_user", "api/v1/user?name=name1&name=name2&name=name3", "user"},
{"Get_empty_path", "api/v1/?user=thinhnx&age=12&addr=12/34/NewYork", ""},
}

for _, tt := range cases {
name := fmt.Sprintf("%s-%s", tt.name, tt.reqPath)
t.Run(name, func(t *testing.T) {
req := &Request{
HandlerPath: "",
Path: tt.reqPath,
}
// check the corresponding elements
outputGP := req.GetQueryPath()
if tt.expectedOutput != outputGP {
t.Errorf("Expected %q, but got %q", tt.expectedOutput, outputGP)
}
})
}
}
4 changes: 4 additions & 0 deletions examples/gno.land/p/demo/mux/request.gno
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func (r *Request) GetVar(key string) string {
case strings.HasPrefix(handlerPart, "{") && strings.HasSuffix(handlerPart, "}"):
parameter := handlerPart[1 : len(handlerPart)-1]
if parameter == key {
// check if this request has query strings or not
if strings.Contains(reqParts[i], "?") {
return strings.Split(reqParts[i], "?")[0]
}
return reqParts[i]
}
default:
Expand Down
3 changes: 3 additions & 0 deletions examples/gno.land/p/demo/mux/request_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func TestRequest_GetVar(t *testing.T) {
{"a/{b}/c/{d}", "a/42/c/1337", "b", "42"},
{"a/{b}/c/{d}", "a/42/c/1337", "d", "1337"},
{"{a}", "foo", "a", "foo"},
{"/api/v1/{user}", "/api/v1/testuser?name=test", "user", "testuser"},
{"/api/{v}/{user}/long/slash", "/api/v1/testuser?name=test/long/slash", "v", "v1"},
{"/api/v1/{user}/long/slash", "/api/v1/testuser?name=test/long/slash", "user", "testuser"},
// TODO: wildcards: a/*/c
// TODO: multiple patterns per slashes: a/{b}-{c}/d
}
Expand Down
3 changes: 3 additions & 0 deletions examples/gno.land/p/demo/mux/router.gno
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func (r *Router) Render(reqPath string) string {
patPart := patParts[i]
reqPart := reqParts[i]

if strings.HasPrefix(patPart, "?") {
continue
}
if patPart == "*" {
continue
}
Expand Down
26 changes: 26 additions & 0 deletions examples/gno.land/p/demo/mux/router_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,39 @@ func TestRouter_Render(t *testing.T) {
res.Write("Hi, earth!")
})

// handler func for query case
router.HandleFunc("hello/{user}/?", func(res *ResponseWriter, req *Request) {
name := req.GetVar("user")
queryPath := req.GetQueryPath()
urlQ := req.Query()

if len(urlQ) != 0 && queryPath != "" {
queryNum := 0
dataToWrite := ""
for key, values := range urlQ {
queryNum++
valuesResult := ""
for _, vl := range values {
valuesResult += vl + ","
}
dataToWrite += " key: " + key + " and value: " + valuesResult
}
res.Write("Hello, " + name + " with path " + queryPath + " ===" + dataToWrite)
} else {
res.Write("Hello, world!")
}
})

cases := []struct {
path string
expectedOutput string
}{
{"hello/Alice", "Hello, Alice!"},
{"hi", "Hi, earth!"},
{"hello/Bob", "Hello, Bob!"},
{"hello/Bob/action?testaction=punch", "Hello, Bob with path action === key: testaction and value: punch,"},
{"hello/Alice/finding?docs=gno&book=gnobook", "Hello, Alice with path finding === key: docs and value: gno, key: book and value: gnobook,"},
{"hello/Celena/finding?color=red&color=blue", "Hello, Celena with path finding === key: color and value: red,blue,"},
// TODO: {"hello", "Hello, world!"},
// TODO: hello/, /hello, hello//Alice, hello/Alice/, hello/Alice/Bob, etc
}
Expand Down
Loading