Skip to content

Commit

Permalink
Add Router.Mutable to update the route handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio Andres Virviescas Santana committed Jun 9, 2020
1 parent 298eced commit f41abad
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 50 deletions.
27 changes: 27 additions & 0 deletions radix/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package radix

import (
"fmt"
)

const (
errSetHandler = "a handler is already registered for path '%s'"
errSetWildcardHandler = "a wildcard handler is already registered for path '%s'"
errWildPathConflict = "'%s' in new path '%s' conflicts with existing wild path '%s' in existing prefix '%s'"
errWildcardConflict = "'%s' in new path '%s' conflicts with existing wildcard '%s' in existing prefix '%s'"
errWildcardSlash = "no / before wildcard in path '%s'"
errWildcardNotAtEnd = "wildcard routes are only allowed at the end of the path in path '%s'"
)

type radixError struct {
msg string
params []interface{}
}

func (err *radixError) Error() string {
return fmt.Sprintf(err.msg, err.params...)
}

func newRadixError(msg string, params ...interface{}) *radixError {
return &radixError{msg, params}
}
63 changes: 26 additions & 37 deletions radix/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,18 @@ func newNode(path string) *node {
}

// conflict raises a panic with some details
func (n *nodeWildcard) conflict(path, fullPath string) {
func (n *nodeWildcard) conflict(path, fullPath string) error {
prefix := fullPath[:strings.LastIndex(fullPath, path)] + n.path

panicf(
"'%s' in new path '%s' conflicts with existing wildcard '%s' in existing prefix '%s'",
path, fullPath, n.path, prefix,
)
return newRadixError(errWildcardConflict, path, fullPath, n.path, prefix)
}

// wildPathConflict raises a panic with some details
func (n *node) wildPathConflict(path, fullPath string) {
func (n *node) wildPathConflict(path, fullPath string) error {
pathSeg := strings.SplitN(path, "/", 2)[0]
prefix := fullPath[:strings.LastIndex(fullPath, path)] + n.path

panicf(
"'%s' in new path '%s' conflicts with existing wildcard '%s' in existing prefix '%s'",
pathSeg, fullPath, n.path, prefix,
)
return newRadixError(errWildPathConflict, pathSeg, fullPath, n.path, prefix)
}

// clone clones the current node in a new pointer
Expand Down Expand Up @@ -65,9 +59,7 @@ func (n node) clone() *node {
copy(cloneNode.paramKeys, n.paramKeys)
}

if n.paramRegex != nil {
cloneNode.paramRegex = n.paramRegex.Copy()
}
cloneNode.paramRegex = n.paramRegex

return cloneNode
}
Expand Down Expand Up @@ -111,9 +103,9 @@ func (n *node) findEndIndexAndValues(path string) (int, []string) {
return end, values
}

func (n *node) setHandler(handler fasthttp.RequestHandler, fullPath string) {
func (n *node) setHandler(handler fasthttp.RequestHandler, fullPath string) (*node, error) {
if n.handler != nil || n.tsr {
panicf("a handle is already registered for path '%s'", fullPath)
return n, newRadixError(errSetHandler, fullPath)
}

n.handler = handler
Expand All @@ -138,9 +130,11 @@ func (n *node) setHandler(handler fasthttp.RequestHandler, fullPath string) {
childTSR.tsr = true
n.children = append(n.children, childTSR)
}

return n, nil
}

func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) *node {
func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) (*node, error) {
end := segmentEndIndex(path, true)
child := newNode(path)

Expand All @@ -156,10 +150,6 @@ func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) *n
if wp.start > 0 {
n.children = append(n.children, child)

// if !child.tsr {
// childParent.handler = nil
// }

return child.insert(path[j:], fullPath, handler)
}

Expand All @@ -171,9 +161,9 @@ func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) *n
child.paramRegex = wp.regex
case wildcard:
if len(path) == end && n.path[len(n.path)-1] != '/' {
panicf("no / before wildcard in path '%s'", fullPath)
return nil, newRadixError(errWildcardSlash, fullPath)
} else if len(path) != end {
panicf("wildcard routes are only allowed at the end of the path in path '%s'", fullPath)
return nil, newRadixError(errWildcardNotAtEnd, fullPath)
}

if n.path != "/" && n.path[len(n.path)-1] == '/' {
Expand All @@ -184,7 +174,11 @@ func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) *n
}

if n.wildcard != nil {
n.wildcard.conflict(path, fullPath)
if n.wildcard.path == path {
return n, newRadixError(errSetWildcardHandler, fullPath)
}

return nil, n.wildcard.conflict(path, fullPath)
}

n.wildcard = &nodeWildcard{
Expand All @@ -193,18 +187,14 @@ func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) *n
handler: handler,
}

return n
return n, nil
}

path = path[wp.end:]

if len(path) > 0 {
n.children = append(n.children, child)

// if !child.tsr {
// childParent.handler = nil
// }

return child.insert(path, fullPath, handler)
}
}
Expand All @@ -224,15 +214,13 @@ func (n *node) insert(path, fullPath string, handler fasthttp.RequestHandler) *n
child.children = append(child.children, childTSR)
}

return child
return child, nil
}

// add adds the handler to node for the given path
func (n *node) add(path, fullPath string, handler fasthttp.RequestHandler) *node {
func (n *node) add(path, fullPath string, handler fasthttp.RequestHandler) (*node, error) {
if n.path == path || len(path) == 0 {
n.setHandler(handler, fullPath)

return n
return n.setHandler(handler, fullPath)
}

for _, child := range n.children {
Expand All @@ -258,8 +246,11 @@ func (n *node) add(path, fullPath string, handler fasthttp.RequestHandler) *node

if len(path) == wp.end && isParam && hasHandler {
// The current segment is a param and it's duplicated
if child.path == path {
return child, newRadixError(errSetHandler, fullPath)
}

child.wildPathConflict(path, fullPath)
return nil, child.wildPathConflict(path, fullPath)
}

if len(path) > i {
Expand All @@ -275,9 +266,7 @@ func (n *node) add(path, fullPath string, handler fasthttp.RequestHandler) *node
n.tsr = true
}

child.setHandler(handler, fullPath)

return child
return child.setHandler(handler, fullPath)
}

return n.insert(path, fullPath, handler)
Expand Down
34 changes: 22 additions & 12 deletions radix/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,15 @@ func TestTreeInvalidNodeType(t *testing.T) {
}

func TestTreeWildcardConflictEx(t *testing.T) {
routes := [...]string{
"/con{tact}",
"/who/are/{you:*}",
"/who/foo/hello",
"/whose/{users}/{name}",
"/{filepath:*}",
"/{id}",
}

conflicts := []struct {
route string

Expand All @@ -650,7 +659,17 @@ func TestTreeWildcardConflictEx(t *testing.T) {
{
route: "/con{tact}",
wantErr: true,
wantErrText: "'{tact}' in new path '/con{tact}' conflicts with existing wildcard '{tact}' in existing prefix '/con{tact}'",
wantErrText: "a handler is already registered for path '/con{tact}'",
},
{
route: "/con{something}",
wantErr: true,
wantErrText: "'{something}' in new path '/con{something}' conflicts with existing wild path '{tact}' in existing prefix '/con{tact}'",
},
{
route: "/who/are/{you:*}",
wantErr: true,
wantErrText: "a wildcard handler is already registered for path '/who/are/{you:*}'",
},
{
route: "/who/are/{me:*}",
Expand All @@ -660,7 +679,7 @@ func TestTreeWildcardConflictEx(t *testing.T) {
{
route: "/who/foo/hello",
wantErr: true,
wantErrText: "a handle is already registered for path '/who/foo/hello'",
wantErrText: "a handler is already registered for path '/who/foo/hello'",
},
{
route: "/{static:*}",
Expand All @@ -675,7 +694,7 @@ func TestTreeWildcardConflictEx(t *testing.T) {
{
route: "/{user}/",
wantErr: true,
wantErrText: "'{user}' in new path '/{user}/' conflicts with existing wildcard '{id}' in existing prefix '/{id}'",
wantErrText: "'{user}' in new path '/{user}/' conflicts with existing wild path '{id}' in existing prefix '/{id}'",
},
{
route: "/prefix{filepath:*}",
Expand All @@ -690,15 +709,6 @@ func TestTreeWildcardConflictEx(t *testing.T) {
// panic which threw by 'addRoute' function.
tree := New()

routes := [...]string{
"/con{tact}",
"/who/are/{you:*}",
"/who/foo/hello",
"/whose/{users}/{name}",
"/{filepath:*}",
"/{id}",
}

for _, route := range routes {
tree.Add(route, fakeHandler(route))
}
Expand Down
17 changes: 16 additions & 1 deletion radix/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,22 @@ func (t *Tree) Add(path string, handler fasthttp.RequestHandler) {
path = path[i:]
}

t.root.add(path, fullPath, handler)
n, err := t.root.add(path, fullPath, handler)
if err != nil {
radixErr := err.(*radixError)
if t.Mutable && !n.tsr {
switch radixErr.msg {
case errSetHandler:
n.handler = handler
return
case errSetWildcardHandler:
n.wildcard.handler = handler
return
}
}

panic(err)
}

if len(t.root.path) == 0 {
t.root = t.root.children[0]
Expand Down
36 changes: 36 additions & 0 deletions radix/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,42 @@ func Test_TreeNilHandler(t *testing.T) {
}
}

func Test_TreeMutable(t *testing.T) {
routes := []string{
"/",
"/api/{version}",
"/{filepath:*}",
"/user{user:a-Z+}",
}

handler := generateHandler()
tree := New()

for _, route := range routes {
tree.Add(route, handler)

err := catchPanic(func() {
tree.Add(route, handler)
})

if err == nil {
t.Errorf("Route '%s' - Expected panic", route)
}
}

tree.Mutable = true

for _, route := range routes {
err := catchPanic(func() {
tree.Add(route, handler)
})

if err != nil {
t.Errorf("Route '%s' - Unexpected panic: %v", route, err)
}
}
}

func Benchmark_Get(b *testing.B) {
handler := func(ctx *fasthttp.RequestCtx) {}

Expand Down
3 changes: 3 additions & 0 deletions radix/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ type wildPath struct {
// Tree is a routes storage
type Tree struct {
root *node

// If enabled, the node handler could be updated
Mutable bool
}
11 changes: 11 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ func (r *Router) saveMatchedRoutePath(path string, handler fasthttp.RequestHandl
}
}

// Mutable allows updating the route handler
//
// It's disabled by default
//
// WARNING: Use with care. It could generate unexpected behaviours
func (r *Router) Mutable(v bool) {
for method := range r.trees {
r.trees[method].Mutable = v
}
}

// GET is a shortcut for router.Handle(fasthttp.MethodGet, path, handler)
func (r *Router) GET(path string, handler fasthttp.RequestHandler) {
r.Handle(fasthttp.MethodGet, path, handler)
Expand Down

0 comments on commit f41abad

Please sign in to comment.