This commit is contained in:
Gerasimos (Makis) Maropoulos 2022-09-18 01:52:30 +03:00
parent 9e8a58bf3b
commit 3d99983d96
No known key found for this signature in database
GPG Key ID: 403EEB7885C79503
8 changed files with 111 additions and 49 deletions

View File

@ -369,5 +369,12 @@ func main() {
// should differ e.g.
// /path/{name:string}
// /path/{id:uint}
//
// Note:
// If * path part is declared at the end of the route path, then
// it's considered a wildcard (same as {p:path}). In order to declare
// literal * and over pass this limitation use the string's path parameter 'eq' function
// as shown below:
// app.Get("/*/*/{p:string eq(*)}", handler) <- This will match only: /*/*/* and not /*/*/anything.
app.Listen(":8080")
}

View File

@ -98,19 +98,22 @@ func joinPath(path1 string, path2 string) string {
// 6. Remove trailing '/'.
//
// The returned path ends in a slash only if it is the root "/".
func cleanPath(s string) string {
// The function does not modify the dynamic path parts.
func cleanPath(path string) string {
// note that we don't care about the performance here, it's before the server ran.
if s == "" || s == "." {
if path == "" || path == "." {
return "/"
}
// remove suffix "/", if it's root "/" then it will add it as a prefix below.
if lidx := len(s) - 1; s[lidx] == '/' {
s = s[:lidx]
if lidx := len(path) - 1; path[lidx] == '/' {
path = path[:lidx]
}
// prefix with "/".
s = prefix(s, "/")
path = prefix(path, "/")
s := []rune(path)
// If you're learning go through Iris I will ask you to ignore the
// following part, it's not the recommending way to do that,
@ -138,46 +141,34 @@ func cleanPath(s string) string {
// when inside {} then don't try to clean it.
if !insideMacro {
if s[i] == '/' {
if len(s)-1 >= i+1 && s[i+1] == '/' { // we have "//".
bckp := s
s = bckp[:i] + "/"
// forward two, we ignore the second "/" in the raw.
i = i + 2
if len(bckp)-1 >= i {
s += bckp[i:]
}
if s[i] == '\\' {
s[i] = '/'
if len(s)-1 > i+1 && s[i+1] == '\\' {
s = deleteCharacter(s, i+1)
} else {
i-- // set to minus in order for the next check to be applied for prev tokens too.
}
// if we have just a single slash then continue.
continue
}
if s[i] == '\\' { // this will catch "\\" and "\".
bckp := s
s = bckp[:i] + "/"
if len(bckp)-1 >= i+1 {
s += bckp[i+1:]
i++
}
if len(s)-1 > i && s[i] == '\\' {
bckp := s
s = bckp[:i]
if len(bckp)-1 >= i+2 {
s = bckp[:i-1] + bckp[i+1:]
i++
}
}
if s[i] == '/' && len(s)-1 > i+1 && s[i+1] == '/' {
s[i] = '/'
s = deleteCharacter(s, i+1)
i--
continue
}
}
}
return s
if len(s) > 1 && s[len(s)-1] == '/' { // remove any last //.
s = s[:len(s)-1]
}
return string(s)
}
func deleteCharacter(s []rune, index int) []rune {
return append(s[0:index], s[index+1:]...)
}
const (

View File

@ -47,6 +47,18 @@ func TestCleanPath(t *testing.T) {
"/single/{id:uint64}",
"/single/{id:uint64}",
},
{
"0\\\\\\0",
"/0/0",
},
{
"*\\*\\*",
"/*/*/*",
},
{
"\\",
"/",
},
}
for i, tt := range tests {

View File

@ -90,12 +90,14 @@ type Route struct {
// handlers are being changed to validate the macros at serve time, if needed.
func NewRoute(p Party, statusErrorCode int, method, subdomain, unparsedPath string,
handlers context.Handlers, macros macro.Macros) (*Route, error) {
tmpl, err := macro.Parse(unparsedPath, macros)
path := cleanPath(unparsedPath) // required. Before macro template parse as the cleanPath does not modify the dynamic path route parts.
tmpl, err := macro.Parse(path, macros)
if err != nil {
return nil, err
}
path := convertMacroTmplToNodePath(tmpl)
path = convertMacroTmplToNodePath(tmpl)
// prepend the macro handler to the route, now,
// right before the register to the tree, so APIBuilder#UseGlobal will work as expected.
if handler.CanMakeHandler(tmpl) {
@ -103,7 +105,6 @@ func NewRoute(p Party, statusErrorCode int, method, subdomain, unparsedPath stri
handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...)
}
path = cleanPath(path) // maybe unnecessary here.
defaultName := method + subdomain + tmpl.Src
if statusErrorCode > 0 {
defaultName = fmt.Sprintf("%d_%s", statusErrorCode, defaultName)
@ -506,7 +507,7 @@ func (r *Route) Trace(w io.Writer, stoppedIndex int) {
// s := fmt.Sprintf("%s: %s", pio.Rich(title, color), path)
pio.WriteRich(w, title, color)
path := r.Tmpl().Src
path := r.tmpl.Src
if path == "" {
path = "/"
}

View File

@ -9,9 +9,13 @@ import (
const (
// ParamStart the character in string representation where the underline router starts its dynamic named parameter.
ParamStart = ":"
// paramStartCharacter is the character as rune of ParamStart.
paramStartCharacter = ':'
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
// path parameter.
WildcardParamStart = "*"
// wildcardParamStartCharacter is the character as rune of WildcardParamStart.
wildcardParamStartCharacter = '*'
)
// An iris-specific identical version of the https://github.com/kataras/muxie version 1.0.0 released at 15 Oct 2018
@ -112,6 +116,9 @@ func slowPathSplit(path string) []string {
func (tr *trie) insert(path string, route context.RouteReadOnly, handlers context.Handlers) {
input := slowPathSplit(path)
if len(input) == 0 {
return
}
n := tr.root
if path == pathSep {
@ -121,9 +128,13 @@ func (tr *trie) insert(path string, route context.RouteReadOnly, handlers contex
var paramKeys []string
for _, s := range input {
if len(s) == 0 {
continue
}
c := s[0]
if isParam, isWildcard := c == ParamStart[0], c == WildcardParamStart[0]; isParam || isWildcard {
if isParam, isWildcard := c == paramStartCharacter, c == wildcardParamStartCharacter; isParam || isWildcard {
n.hasDynamicChild = true
paramKeys = append(paramKeys, s[1:]) // without : or *.
@ -166,7 +177,6 @@ func (tr *trie) insert(path string, route context.RouteReadOnly, handlers contex
}
n.staticKey = path[:i]
// fmt.Printf("trie.insert: (whole path=%v) Path: %s, Route name: %s, Handlers len: %d\n", n.end, n.key, route.Name(), len(handlers))
}
@ -192,15 +202,26 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
for {
if i == end || q[i] == pathSepB {
if child := n.getChild(q[start:i]); child != nil {
segment := q[start:i]
if child := n.getChild(segment); child != nil {
n = child
// Possible reserved param character, should catch it as
// dynamic node instead of static-path based.
if segment == ParamStart { // len(n.paramKeys) > 0 && (segment == ParamStart || segment == WildcardParamStart)
if ln := len(paramValues); cap(paramValues) > ln {
paramValues = paramValues[:ln+1]
paramValues[ln] = segment
} else {
paramValues = append(paramValues, segment)
}
}
} else if n.childNamedParameter {
n = n.getChild(ParamStart)
if ln := len(paramValues); cap(paramValues) > ln {
paramValues = paramValues[:ln+1]
paramValues[ln] = q[start:i]
paramValues[ln] = segment
} else {
paramValues = append(paramValues, q[start:i])
paramValues = append(paramValues, segment)
}
} else if n.childWildcardParameter {
n = n.getChild(WildcardParamStart)
@ -213,7 +234,7 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
break
} else {
n = n.findClosestParentWildcardNode()
if n != nil {
if n != nil && len(n.paramKeys) > 0 {
// means that it has :param/static and *wildcard, we go trhough the :param
// but the next path segment is not the /static, so go back to *wildcard
// instead of not found.
@ -248,7 +269,7 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
if n == nil || !n.end {
if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above).
if n = n.findClosestParentWildcardNode(); n != nil {
if n = n.findClosestParentWildcardNode(); n != nil && len(n.paramKeys) > 0 {
params.Set(n.paramKeys[0], q[len(n.staticKey):])
return n
}
@ -263,6 +284,10 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
// the /other2/*myparam and not the root wildcard, which is what we want.
//
n = tr.root.getChild(WildcardParamStart)
if len(n.paramKeys) == 0 { // fix crashes on /*/*/*.
return nil
}
params.Set(n.paramKeys[0], q[1:])
return n
}

View File

@ -107,6 +107,7 @@ func MakeFilter(tmpl macro.Template) context.Filter {
entry, found := ctx.Params().Store.GetEntryAt(p.Index)
if !found {
// should never happen.
ctx.StatusCode(p.ErrCode) // status code can change from an error handler, set it here.
return false
}

View File

@ -39,7 +39,7 @@ func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement,
}
// if we have param type path but it's not the last path part
if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 {
return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very end of a path", s, stmt.Type.Indent())
return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very end of a path once", s, stmt.Type.Indent())
}
statements = append(statements, stmt)

View File

@ -50,6 +50,31 @@ var (
return func(paramValue string) bool {
return max >= len(paramValue)
}
}).
// checks if param value's matches the given input
RegisterFunc("eq", func(s string) func(string) bool {
return func(paramValue string) bool {
return paramValue == s
}
}).
// checks if param value's matches at least one of the inputs
RegisterFunc("eqor", func(texts []string) func(string) bool {
if len(texts) == 1 {
text := texts[0]
return func(paramValue string) bool {
return paramValue == text
}
}
return func(paramValue string) bool {
for _, s := range texts {
if paramValue == s {
return true
}
}
return false
}
})
// Int or number type