-
Notifications
You must be signed in to change notification settings - Fork 375
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into dev/moul/r/sys/params
- Loading branch information
Showing
15 changed files
with
6,729 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module gno.land/p/moul/realmpath | ||
|
||
require ( | ||
gno.land/p/demo/uassert v0.0.0-latest | ||
gno.land/p/demo/urequire v0.0.0-latest | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Package realmpath is a lightweight Render.path parsing and link generation | ||
// library with an idiomatic API, closely resembling that of net/url. | ||
// | ||
// This package provides utilities for parsing request paths and query | ||
// parameters, allowing you to extract path segments and manipulate query | ||
// values. | ||
// | ||
// Example usage: | ||
// | ||
// import "gno.land/p/moul/realmpath" | ||
// | ||
// func Render(path string) string { | ||
// // Parsing a sample path with query parameters | ||
// path = "hello/world?foo=bar&baz=foobar" | ||
// req := realmpath.Parse(path) | ||
// | ||
// // Accessing parsed path and query parameters | ||
// println(req.Path) // Output: hello/world | ||
// println(req.PathPart(0)) // Output: hello | ||
// println(req.PathPart(1)) // Output: world | ||
// println(req.Query.Get("foo")) // Output: bar | ||
// println(req.Query.Get("baz")) // Output: foobar | ||
// | ||
// // Rebuilding the URL | ||
// println(req.String()) // Output: /r/current/realm:hello/world?baz=foobar&foo=bar | ||
// } | ||
package realmpath | ||
|
||
import ( | ||
"net/url" | ||
"std" | ||
"strings" | ||
) | ||
|
||
const chainDomain = "gno.land" // XXX: std.ChainDomain (#2911) | ||
|
||
// Request represents a parsed request. | ||
type Request struct { | ||
Path string // The path of the request | ||
Query url.Values // The parsed query parameters | ||
Realm string // The realm associated with the request | ||
} | ||
|
||
// Parse takes a raw path string and returns a Request object. | ||
// It splits the path into its components and parses any query parameters. | ||
func Parse(rawPath string) *Request { | ||
// Split the raw path into path and query components | ||
path, query := splitPathAndQuery(rawPath) | ||
|
||
// Parse the query string into url.Values | ||
queryValues, _ := url.ParseQuery(query) | ||
|
||
return &Request{ | ||
Path: path, // Set the path | ||
Query: queryValues, // Set the parsed query values | ||
} | ||
} | ||
|
||
// PathParts returns the segments of the path as a slice of strings. | ||
// It trims leading and trailing slashes and splits the path by slashes. | ||
func (r *Request) PathParts() []string { | ||
return strings.Split(strings.Trim(r.Path, "/"), "/") | ||
} | ||
|
||
// PathPart returns the specified part of the path. | ||
// If the index is out of bounds, it returns an empty string. | ||
func (r *Request) PathPart(index int) string { | ||
parts := r.PathParts() // Get the path segments | ||
if index < 0 || index >= len(parts) { | ||
return "" // Return empty if index is out of bounds | ||
} | ||
return parts[index] // Return the specified path part | ||
} | ||
|
||
// String rebuilds the URL from the path and query values. | ||
// If the Realm is not set, it automatically retrieves the current realm path. | ||
func (r *Request) String() string { | ||
// Automatically set the Realm if it is not already defined | ||
if r.Realm == "" { | ||
r.Realm = std.CurrentRealm().PkgPath() // Get the current realm path | ||
} | ||
|
||
// Rebuild the path using the realm and path parts | ||
relativePkgPath := strings.TrimPrefix(r.Realm, chainDomain) // Trim the chain domain prefix | ||
reconstructedPath := relativePkgPath + ":" + strings.Join(r.PathParts(), "/") | ||
|
||
// Rebuild the query string | ||
queryString := r.Query.Encode() // Encode the query parameters | ||
if queryString != "" { | ||
return reconstructedPath + "?" + queryString // Return the full URL with query | ||
} | ||
return reconstructedPath // Return the path without query parameters | ||
} | ||
|
||
func splitPathAndQuery(rawPath string) (string, string) { | ||
if idx := strings.Index(rawPath, "?"); idx != -1 { | ||
return rawPath[:idx], rawPath[idx+1:] // Split at the first '?' found | ||
} | ||
return rawPath, "" // No query string present | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package realmpath_test | ||
|
||
import ( | ||
"net/url" | ||
"std" | ||
"testing" | ||
|
||
"gno.land/p/demo/uassert" | ||
"gno.land/p/demo/urequire" | ||
"gno.land/p/moul/realmpath" | ||
) | ||
|
||
func TestExample(t *testing.T) { | ||
std.TestSetRealm(std.NewCodeRealm("gno.land/r/lorem/ipsum")) | ||
|
||
// initial parsing | ||
path := "hello/world?foo=bar&baz=foobar" | ||
req := realmpath.Parse(path) | ||
urequire.False(t, req == nil, "req should not be nil") | ||
uassert.Equal(t, req.Path, "hello/world") | ||
uassert.Equal(t, req.Query.Get("foo"), "bar") | ||
uassert.Equal(t, req.Query.Get("baz"), "foobar") | ||
uassert.Equal(t, req.String(), "/r/lorem/ipsum:hello/world?baz=foobar&foo=bar") | ||
|
||
// alter query | ||
req.Query.Set("hey", "salut") | ||
uassert.Equal(t, req.String(), "/r/lorem/ipsum:hello/world?baz=foobar&foo=bar&hey=salut") | ||
|
||
// alter path | ||
req.Path = "bye/ciao" | ||
uassert.Equal(t, req.String(), "/r/lorem/ipsum:bye/ciao?baz=foobar&foo=bar&hey=salut") | ||
} | ||
|
||
func TestParse(t *testing.T) { | ||
std.TestSetRealm(std.NewCodeRealm("gno.land/r/lorem/ipsum")) | ||
|
||
tests := []struct { | ||
rawPath string | ||
realm string // optional | ||
expectedPath string | ||
expectedQuery url.Values | ||
expectedString string | ||
}{ | ||
{ | ||
rawPath: "hello/world?foo=bar&baz=foobar", | ||
expectedPath: "hello/world", | ||
expectedQuery: url.Values{ | ||
"foo": []string{"bar"}, | ||
"baz": []string{"foobar"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:hello/world?baz=foobar&foo=bar", | ||
}, | ||
{ | ||
rawPath: "api/v1/resource?search=test&limit=10", | ||
expectedPath: "api/v1/resource", | ||
expectedQuery: url.Values{ | ||
"search": []string{"test"}, | ||
"limit": []string{"10"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:api/v1/resource?limit=10&search=test", | ||
}, | ||
{ | ||
rawPath: "singlepath", | ||
expectedPath: "singlepath", | ||
expectedQuery: url.Values{}, | ||
expectedString: "/r/lorem/ipsum:singlepath", | ||
}, | ||
{ | ||
rawPath: "path/with/trailing/slash/", | ||
expectedPath: "path/with/trailing/slash/", | ||
expectedQuery: url.Values{}, | ||
expectedString: "/r/lorem/ipsum:path/with/trailing/slash", | ||
}, | ||
{ | ||
rawPath: "emptyquery?", | ||
expectedPath: "emptyquery", | ||
expectedQuery: url.Values{}, | ||
expectedString: "/r/lorem/ipsum:emptyquery", | ||
}, | ||
{ | ||
rawPath: "path/with/special/characters/?key=val%20ue&anotherKey=with%21special%23chars", | ||
expectedPath: "path/with/special/characters/", | ||
expectedQuery: url.Values{ | ||
"key": []string{"val ue"}, | ||
"anotherKey": []string{"with!special#chars"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:path/with/special/characters?anotherKey=with%21special%23chars&key=val+ue", | ||
}, | ||
{ | ||
rawPath: "path/with/empty/key?keyEmpty&=valueEmpty", | ||
expectedPath: "path/with/empty/key", | ||
expectedQuery: url.Values{ | ||
"keyEmpty": []string{""}, | ||
"": []string{"valueEmpty"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:path/with/empty/key?=valueEmpty&keyEmpty=", | ||
}, | ||
{ | ||
rawPath: "path/with/multiple/empty/keys?=empty1&=empty2", | ||
expectedPath: "path/with/multiple/empty/keys", | ||
expectedQuery: url.Values{ | ||
"": []string{"empty1", "empty2"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:path/with/multiple/empty/keys?=empty1&=empty2", | ||
}, | ||
{ | ||
rawPath: "path/with/percent-encoded/%20space?query=hello%20world", | ||
expectedPath: "path/with/percent-encoded/%20space", // XXX: should we decode? | ||
expectedQuery: url.Values{ | ||
"query": []string{"hello world"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:path/with/percent-encoded/%20space?query=hello+world", | ||
}, | ||
{ | ||
rawPath: "path/with/very/long/query?key1=value1&key2=value2&key3=value3&key4=value4&key5=value5&key6=value6", | ||
expectedPath: "path/with/very/long/query", | ||
expectedQuery: url.Values{ | ||
"key1": []string{"value1"}, | ||
"key2": []string{"value2"}, | ||
"key3": []string{"value3"}, | ||
"key4": []string{"value4"}, | ||
"key5": []string{"value5"}, | ||
"key6": []string{"value6"}, | ||
}, | ||
expectedString: "/r/lorem/ipsum:path/with/very/long/query?key1=value1&key2=value2&key3=value3&key4=value4&key5=value5&key6=value6", | ||
}, | ||
{ | ||
rawPath: "custom/realm?foo=bar&baz=foobar", | ||
realm: "gno.land/r/foo/bar", | ||
expectedPath: "custom/realm", | ||
expectedQuery: url.Values{ | ||
"foo": []string{"bar"}, | ||
"baz": []string{"foobar"}, | ||
}, | ||
expectedString: "/r/foo/bar:custom/realm?baz=foobar&foo=bar", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.rawPath, func(t *testing.T) { | ||
req := realmpath.Parse(tt.rawPath) | ||
req.Realm = tt.realm // set optional realm | ||
urequire.False(t, req == nil, "req should not be nil") | ||
uassert.Equal(t, req.Path, tt.expectedPath) | ||
urequire.Equal(t, len(req.Query), len(tt.expectedQuery)) | ||
uassert.Equal(t, req.Query.Encode(), tt.expectedQuery.Encode()) | ||
// XXX: uassert.Equal(t, req.Query, tt.expectedQuery) | ||
uassert.Equal(t, req.String(), tt.expectedString) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.