Reimplement generics
This commit is contained in:
parent
0dc5189e35
commit
34e8d28e95
|
@ -3,6 +3,8 @@
|
|||
A radix-tree based no-allocation router in Go. All credit to Eduard Urbach for his incredible work. This router sports
|
||||
a fancy PATRICIA tree structure for efficient string lookups. It also has ***zero dependencies***!
|
||||
|
||||
The router has a generic data structure, so any kind of handler can be used.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
|
|
34
router.go
34
router.go
|
@ -1,30 +1,30 @@
|
|||
package router
|
||||
|
||||
type Router struct {
|
||||
get Tree
|
||||
post Tree
|
||||
delete Tree
|
||||
put Tree
|
||||
patch Tree
|
||||
head Tree
|
||||
connect Tree
|
||||
trace Tree
|
||||
options Tree
|
||||
type Router[T any] struct {
|
||||
get Tree[T]
|
||||
post Tree[T]
|
||||
delete Tree[T]
|
||||
put Tree[T]
|
||||
patch Tree[T]
|
||||
head Tree[T]
|
||||
connect Tree[T]
|
||||
trace Tree[T]
|
||||
options Tree[T]
|
||||
}
|
||||
|
||||
// Create a new Router containing trees for every HTTP method.
|
||||
func New() *Router {
|
||||
return &Router{}
|
||||
func New[T any]() *Router[T] {
|
||||
return &Router[T]{}
|
||||
}
|
||||
|
||||
// Registers a new handler for the given method and path.
|
||||
func (router *Router) Add(method string, path string, handler string) {
|
||||
func (router *Router[T]) Add(method string, path string, handler T) {
|
||||
tree := router.selectTree(method)
|
||||
tree.Add(path, handler)
|
||||
}
|
||||
|
||||
// Finds the handler and parameters for the given route.
|
||||
func (router *Router) Lookup(method string, path string) (string, []Parameter) {
|
||||
func (router *Router[T]) Lookup(method string, path string) (T, []Parameter) {
|
||||
if method[0] == 'G' {
|
||||
return router.get.Lookup(path)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func (router *Router) Lookup(method string, path string) (string, []Parameter) {
|
|||
}
|
||||
|
||||
// Finds the handler and parameters for the given route without using any memory allocations.
|
||||
func (router *Router) LookupNoAlloc(method string, path string, addParameter func(string, string)) string {
|
||||
func (router *Router[T]) LookupNoAlloc(method string, path string, addParameter func(string, string)) T {
|
||||
if method[0] == 'G' {
|
||||
return router.get.LookupNoAlloc(path, addParameter)
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func (router *Router) LookupNoAlloc(method string, path string, addParameter fun
|
|||
}
|
||||
|
||||
// Traverses all trees and calls the given function on every node.
|
||||
func (router *Router) Map(transform func(string) string) {
|
||||
func (router *Router[T]) Map(transform func(T) T) {
|
||||
router.get.Map(transform)
|
||||
router.post.Map(transform)
|
||||
router.delete.Map(transform)
|
||||
|
@ -57,7 +57,7 @@ func (router *Router) Map(transform func(string) string) {
|
|||
}
|
||||
|
||||
// Returns the tree of the given HTTP method.
|
||||
func (router *Router) selectTree(method string) *Tree {
|
||||
func (router *Router[T]) selectTree(method string) *Tree[T] {
|
||||
switch method {
|
||||
case "GET":
|
||||
return &router.get
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
func BenchmarkBlog(b *testing.B) {
|
||||
routes := routes("blog.txt")
|
||||
r := router.New()
|
||||
r := router.New[string]()
|
||||
|
||||
for _, route := range routes {
|
||||
r.Add(route.Method, route.Path, "")
|
||||
|
@ -44,7 +44,7 @@ func BenchmarkBlog(b *testing.B) {
|
|||
|
||||
func BenchmarkGithub(b *testing.B) {
|
||||
routes := routes("github.txt")
|
||||
r := router.New()
|
||||
r := router.New[string]()
|
||||
|
||||
for _, route := range routes {
|
||||
r.Add(route.Method, route.Path, "")
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func TestHello(t *testing.T) {
|
||||
r := router.New()
|
||||
r := router.New[string]()
|
||||
r.Add("GET", "/blog", "Blog")
|
||||
r.Add("GET", "/blog/post", "Blog post")
|
||||
|
||||
|
|
18
tree.go
18
tree.go
|
@ -1,12 +1,12 @@
|
|||
package router
|
||||
|
||||
// Super-fancy radix tree
|
||||
type Tree struct {
|
||||
root TreeNode
|
||||
type Tree[T any] struct {
|
||||
root TreeNode[T]
|
||||
}
|
||||
|
||||
// Adds a new element to the tree
|
||||
func (tree *Tree) Add(path string, data string) {
|
||||
func (tree *Tree[T]) Add(path string, data T) {
|
||||
// Search tree for equal parts until we can no longer proceed
|
||||
i := 0
|
||||
offset := 0
|
||||
|
@ -79,7 +79,7 @@ func (tree *Tree) Add(path string, data string) {
|
|||
}
|
||||
|
||||
// Lookup finds the data for the given path.
|
||||
func (tree *Tree) Lookup(path string) (string, []Parameter) {
|
||||
func (tree *Tree[T]) Lookup(path string) (T, []Parameter) {
|
||||
var params []Parameter
|
||||
|
||||
data := tree.LookupNoAlloc(path, func(key string, value string) {
|
||||
|
@ -90,11 +90,11 @@ func (tree *Tree) Lookup(path string) (string, []Parameter) {
|
|||
}
|
||||
|
||||
// Finds the data for the given path without using any memory allocations.
|
||||
func (tree *Tree) LookupNoAlloc(path string, addParameter func(key string, value string)) string {
|
||||
func (tree *Tree[T]) LookupNoAlloc(path string, addParameter func(key string, value string)) T {
|
||||
var (
|
||||
i uint
|
||||
wildcardPath string
|
||||
wildcard *TreeNode
|
||||
wildcard *TreeNode[T]
|
||||
node = &tree.root
|
||||
)
|
||||
|
||||
|
@ -183,13 +183,13 @@ notFound:
|
|||
return wildcard.data
|
||||
}
|
||||
|
||||
var empty string
|
||||
var empty T
|
||||
return empty
|
||||
}
|
||||
|
||||
// Binds all handlers to a new one provided by the callback.
|
||||
func (tree *Tree) Map(transform func(string) string) {
|
||||
tree.root.each(func(node *TreeNode) {
|
||||
func (tree *Tree[T]) Map(transform func(T) T) {
|
||||
tree.root.each(func(node *TreeNode[T]) {
|
||||
node.data = transform(node.data)
|
||||
})
|
||||
}
|
||||
|
|
38
treeNode.go
38
treeNode.go
|
@ -10,12 +10,12 @@ const (
|
|||
)
|
||||
|
||||
// A node on our radix tree
|
||||
type TreeNode struct {
|
||||
type TreeNode[T any] struct {
|
||||
prefix string
|
||||
data string
|
||||
children []*TreeNode
|
||||
parameter *TreeNode
|
||||
wildcard *TreeNode
|
||||
data T
|
||||
children []*TreeNode[T]
|
||||
parameter *TreeNode[T]
|
||||
wildcard *TreeNode[T]
|
||||
indexes []uint8
|
||||
start uint8
|
||||
end uint8
|
||||
|
@ -26,7 +26,7 @@ type TreeNode struct {
|
|||
// node with the given path and data. If path is empty, it will
|
||||
// not create another child node and instead assign the data
|
||||
// directly to the node.
|
||||
func (node *TreeNode) split(index int, path string, data string) {
|
||||
func (node *TreeNode[T]) split(index int, path string, data T) {
|
||||
// Create split node with the remaining string
|
||||
splitNode := node.clone(node.prefix[index:])
|
||||
|
||||
|
@ -48,8 +48,8 @@ func (node *TreeNode) split(index int, path string, data string) {
|
|||
}
|
||||
|
||||
// Clone the node with a new prefix
|
||||
func (node *TreeNode) clone(prefix string) *TreeNode {
|
||||
return &TreeNode{
|
||||
func (node *TreeNode[T]) clone(prefix string) *TreeNode[T] {
|
||||
return &TreeNode[T]{
|
||||
prefix: prefix,
|
||||
data: node.data,
|
||||
children: node.children,
|
||||
|
@ -63,8 +63,8 @@ func (node *TreeNode) clone(prefix string) *TreeNode {
|
|||
}
|
||||
|
||||
// Reset the node, set the prefix
|
||||
func (node *TreeNode) reset(prefix string) {
|
||||
var empty string
|
||||
func (node *TreeNode[T]) reset(prefix string) {
|
||||
var empty T
|
||||
node.prefix = prefix
|
||||
node.data = empty
|
||||
node.children = nil
|
||||
|
@ -77,7 +77,7 @@ func (node *TreeNode) reset(prefix string) {
|
|||
}
|
||||
|
||||
// Append the given path to the tree
|
||||
func (node *TreeNode) append(path string, data string) {
|
||||
func (node *TreeNode[T]) append(path string, data T) {
|
||||
// At this point, all we know is that somewhere
|
||||
// in the remaining string we have parameters.
|
||||
// node: /user|
|
||||
|
@ -106,7 +106,7 @@ func (node *TreeNode) append(path string, data string) {
|
|||
return
|
||||
}
|
||||
|
||||
child := &TreeNode{
|
||||
child := &TreeNode[T]{
|
||||
prefix: path,
|
||||
data: data,
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ func (node *TreeNode) append(path string, data string) {
|
|||
paramEnd = len(path)
|
||||
}
|
||||
|
||||
child := &TreeNode{
|
||||
child := &TreeNode[T]{
|
||||
prefix: path[1:paramEnd],
|
||||
kind: path[paramStart],
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ func (node *TreeNode) append(path string, data string) {
|
|||
}
|
||||
|
||||
// Add a normal node with the path before the parameter start.
|
||||
child := &TreeNode{
|
||||
child := &TreeNode[T]{
|
||||
prefix: path[:paramStart],
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,7 @@ func (node *TreeNode) append(path string, data string) {
|
|||
}
|
||||
|
||||
// Add a child tree node
|
||||
func (node *TreeNode) addChild(child *TreeNode) {
|
||||
func (node *TreeNode[T]) addChild(child *TreeNode[T]) {
|
||||
if len(node.children) == 0 {
|
||||
node.children = append(node.children, nil)
|
||||
}
|
||||
|
@ -213,19 +213,19 @@ func (node *TreeNode) addChild(child *TreeNode) {
|
|||
node.children[index] = child
|
||||
}
|
||||
|
||||
func (node *TreeNode) addTrailingSlash(data string) {
|
||||
func (node *TreeNode[T]) addTrailingSlash(data T) {
|
||||
if strings.HasSuffix(node.prefix, "/") || node.kind == wildcard || (separator >= node.start && separator < node.end && node.indexes[separator-node.start] != 0) {
|
||||
return
|
||||
}
|
||||
|
||||
node.addChild(&TreeNode{
|
||||
node.addChild(&TreeNode[T]{
|
||||
prefix: "/",
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// Traverses the tree and calls the given function on every node.
|
||||
func (node *TreeNode) each(callback func(*TreeNode)) {
|
||||
func (node *TreeNode[T]) each(callback func(*TreeNode[T])) {
|
||||
callback(node)
|
||||
|
||||
for _, child := range node.children {
|
||||
|
@ -247,7 +247,7 @@ func (node *TreeNode) each(callback func(*TreeNode)) {
|
|||
|
||||
// Called when the node was fully parsed and needs to decide the next control flow.
|
||||
// finish is only called from `tree.Add`.
|
||||
func (node *TreeNode) finish(path string, data string, i int, offset int) (*TreeNode, int, Flow) {
|
||||
func (node *TreeNode[T]) finish(path string, data T, i int, offset int) (*TreeNode[T], int, Flow) {
|
||||
char := path[i]
|
||||
|
||||
if char >= node.start && char < node.end {
|
||||
|
|
Loading…
Reference in New Issue
Block a user