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. // should differ e.g.
// /path/{name:string} // /path/{name:string}
// /path/{id:uint} // /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") app.Listen(":8080")
} }

View File

@ -98,19 +98,22 @@ func joinPath(path1 string, path2 string) string {
// 6. Remove trailing '/'. // 6. Remove trailing '/'.
// //
// The returned path ends in a slash only if it is the root "/". // 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. // note that we don't care about the performance here, it's before the server ran.
if s == "" || s == "." { if path == "" || path == "." {
return "/" return "/"
} }
// remove suffix "/", if it's root "/" then it will add it as a prefix below. // remove suffix "/", if it's root "/" then it will add it as a prefix below.
if lidx := len(s) - 1; s[lidx] == '/' { if lidx := len(path) - 1; path[lidx] == '/' {
s = s[:lidx] path = path[:lidx]
} }
// prefix with "/". // 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 // 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, // 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. // when inside {} then don't try to clean it.
if !insideMacro { if !insideMacro {
if s[i] == '/' { if s[i] == '\\' {
if len(s)-1 >= i+1 && s[i+1] == '/' { // we have "//". s[i] = '/'
bckp := s
s = bckp[:i] + "/" if len(s)-1 > i+1 && s[i+1] == '\\' {
// forward two, we ignore the second "/" in the raw. s = deleteCharacter(s, i+1)
i = i + 2 } else {
if len(bckp)-1 >= i { i-- // set to minus in order for the next check to be applied for prev tokens too.
s += bckp[i:]
}
} }
// if we have just a single slash then continue.
continue
} }
if s[i] == '\\' { // this will catch "\\" and "\". if s[i] == '/' && len(s)-1 > i+1 && s[i+1] == '/' {
bckp := s s[i] = '/'
s = bckp[:i] + "/" s = deleteCharacter(s, i+1)
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++
}
}
continue 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 ( const (

View File

@ -47,6 +47,18 @@ func TestCleanPath(t *testing.T) {
"/single/{id:uint64}", "/single/{id:uint64}",
"/single/{id:uint64}", "/single/{id:uint64}",
}, },
{
"0\\\\\\0",
"/0/0",
},
{
"*\\*\\*",
"/*/*/*",
},
{
"\\",
"/",
},
} }
for i, tt := range tests { 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. // handlers are being changed to validate the macros at serve time, if needed.
func NewRoute(p Party, statusErrorCode int, method, subdomain, unparsedPath string, func NewRoute(p Party, statusErrorCode int, method, subdomain, unparsedPath string,
handlers context.Handlers, macros macro.Macros) (*Route, error) { 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 { if err != nil {
return nil, err return nil, err
} }
path := convertMacroTmplToNodePath(tmpl) path = convertMacroTmplToNodePath(tmpl)
// prepend the macro handler to the route, now, // prepend the macro handler to the route, now,
// right before the register to the tree, so APIBuilder#UseGlobal will work as expected. // right before the register to the tree, so APIBuilder#UseGlobal will work as expected.
if handler.CanMakeHandler(tmpl) { if handler.CanMakeHandler(tmpl) {
@ -103,7 +105,6 @@ func NewRoute(p Party, statusErrorCode int, method, subdomain, unparsedPath stri
handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...)
} }
path = cleanPath(path) // maybe unnecessary here.
defaultName := method + subdomain + tmpl.Src defaultName := method + subdomain + tmpl.Src
if statusErrorCode > 0 { if statusErrorCode > 0 {
defaultName = fmt.Sprintf("%d_%s", statusErrorCode, defaultName) 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) // s := fmt.Sprintf("%s: %s", pio.Rich(title, color), path)
pio.WriteRich(w, title, color) pio.WriteRich(w, title, color)
path := r.Tmpl().Src path := r.tmpl.Src
if path == "" { if path == "" {
path = "/" path = "/"
} }

View File

@ -9,9 +9,13 @@ import (
const ( const (
// ParamStart the character in string representation where the underline router starts its dynamic named parameter. // ParamStart the character in string representation where the underline router starts its dynamic named parameter.
ParamStart = ":" ParamStart = ":"
// paramStartCharacter is the character as rune of ParamStart.
paramStartCharacter = ':'
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
// path parameter. // path parameter.
WildcardParamStart = "*" 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 // 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) { func (tr *trie) insert(path string, route context.RouteReadOnly, handlers context.Handlers) {
input := slowPathSplit(path) input := slowPathSplit(path)
if len(input) == 0 {
return
}
n := tr.root n := tr.root
if path == pathSep { if path == pathSep {
@ -121,9 +128,13 @@ func (tr *trie) insert(path string, route context.RouteReadOnly, handlers contex
var paramKeys []string var paramKeys []string
for _, s := range input { for _, s := range input {
if len(s) == 0 {
continue
}
c := s[0] 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 n.hasDynamicChild = true
paramKeys = append(paramKeys, s[1:]) // without : or *. 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] 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)) // 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 { for {
if i == end || q[i] == pathSepB { 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 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 { } else if n.childNamedParameter {
n = n.getChild(ParamStart) n = n.getChild(ParamStart)
if ln := len(paramValues); cap(paramValues) > ln { if ln := len(paramValues); cap(paramValues) > ln {
paramValues = paramValues[:ln+1] paramValues = paramValues[:ln+1]
paramValues[ln] = q[start:i] paramValues[ln] = segment
} else { } else {
paramValues = append(paramValues, q[start:i]) paramValues = append(paramValues, segment)
} }
} else if n.childWildcardParameter { } else if n.childWildcardParameter {
n = n.getChild(WildcardParamStart) n = n.getChild(WildcardParamStart)
@ -213,7 +234,7 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
break break
} else { } else {
n = n.findClosestParentWildcardNode() 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 // 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 // but the next path segment is not the /static, so go back to *wildcard
// instead of not found. // 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 || !n.end {
if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above). 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):]) params.Set(n.paramKeys[0], q[len(n.staticKey):])
return n 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. // the /other2/*myparam and not the root wildcard, which is what we want.
// //
n = tr.root.getChild(WildcardParamStart) n = tr.root.getChild(WildcardParamStart)
if len(n.paramKeys) == 0 { // fix crashes on /*/*/*.
return nil
}
params.Set(n.paramKeys[0], q[1:]) params.Set(n.paramKeys[0], q[1:])
return n return n
} }

View File

@ -107,6 +107,7 @@ func MakeFilter(tmpl macro.Template) context.Filter {
entry, found := ctx.Params().Store.GetEntryAt(p.Index) entry, found := ctx.Params().Store.GetEntryAt(p.Index)
if !found { if !found {
// should never happen. // should never happen.
ctx.StatusCode(p.ErrCode) // status code can change from an error handler, set it here.
return false 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 we have param type path but it's not the last path part
if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 { 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) statements = append(statements, stmt)

View File

@ -50,6 +50,31 @@ var (
return func(paramValue string) bool { return func(paramValue string) bool {
return max >= len(paramValue) 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 // Int or number type