188 lines
4.4 KiB
Odin
188 lines
4.4 KiB
Odin
#+private package
|
|
package fjord
|
|
|
|
import "base:runtime"
|
|
import "core:path/slashpath"
|
|
import "core:strings"
|
|
import http "http"
|
|
|
|
PathVariable :: struct {
|
|
name: string,
|
|
}
|
|
|
|
Static :: struct {}
|
|
|
|
RouterNodeType :: union {
|
|
Static,
|
|
PathVariable,
|
|
}
|
|
|
|
RouterNode :: struct($Error_Type: typeid) {
|
|
type: RouterNodeType,
|
|
handlers: map[http.Method]#type proc(request: ^http.Request) -> (http.Response, Error_Type),
|
|
children: map[string]^RouterNode(Error_Type),
|
|
}
|
|
|
|
Router :: struct($Error_Type: typeid) {
|
|
root_node: ^RouterNode(Error_Type),
|
|
allocator: runtime.Allocator,
|
|
}
|
|
|
|
@(private = "file")
|
|
router_node_init :: proc(
|
|
router: ^Router($Error_Type),
|
|
router_node: ^RouterNode(Error_Type),
|
|
maybe_path_var_name: Maybe(string),
|
|
) {
|
|
router_node.children = make(
|
|
map[string]^RouterNode(Error_Type),
|
|
router.allocator,
|
|
)
|
|
|
|
router_node.handlers = make(
|
|
map[http.Method]#type proc(
|
|
request: ^http.Request,
|
|
) -> (
|
|
http.Response,
|
|
Error_Type,
|
|
),
|
|
router.allocator,
|
|
)
|
|
|
|
path_var_name, ok := maybe_path_var_name.?
|
|
if ok {
|
|
router_node.type = PathVariable {
|
|
name = path_var_name,
|
|
}
|
|
} else {
|
|
router_node.type = Static{}
|
|
}
|
|
}
|
|
|
|
router_init :: proc(
|
|
router: ^Router($Error_Type),
|
|
allocator: runtime.Allocator,
|
|
) {
|
|
router^ = Router(Error_Type) {
|
|
root_node = new(RouterNode(Error_Type), allocator),
|
|
allocator = allocator,
|
|
}
|
|
|
|
router_node_init(router, router.root_node, nil)
|
|
}
|
|
|
|
router_destroy :: proc(router: ^Router($Error_Type)) {
|
|
stack := make([dynamic]^RouterNode(Error_Type), router.allocator)
|
|
defer delete(stack)
|
|
|
|
append(&stack, router.root_node)
|
|
|
|
for len(stack) > 0 {
|
|
node := pop(&stack)
|
|
for _, child in node.children {
|
|
append(&stack, child)
|
|
}
|
|
|
|
delete(node.handlers)
|
|
delete(node.children)
|
|
free(node)
|
|
}
|
|
}
|
|
|
|
router_lookup :: proc(
|
|
router: ^Router($Error_Type),
|
|
method: http.Method,
|
|
path: []string,
|
|
allocator := context.temp_allocator,
|
|
) -> (
|
|
handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type),
|
|
path_variables: map[string]string,
|
|
ok: bool,
|
|
) {
|
|
path_variables = make(map[string]string, allocator)
|
|
current_node := router.root_node
|
|
|
|
for segment_value in path {
|
|
if child, found := current_node.children[segment_value]; found {
|
|
current_node = child
|
|
continue
|
|
}
|
|
|
|
maybe_path_child: Maybe(^RouterNode(Error_Type)) = nil
|
|
for name, child in current_node.children {
|
|
switch type in child.type {
|
|
case PathVariable:
|
|
maybe_path_child = child
|
|
path_variables[type.name] = segment_value
|
|
break
|
|
case Static:
|
|
}
|
|
}
|
|
|
|
if path_child, ok := maybe_path_child.?; ok {
|
|
current_node = path_child
|
|
} else {
|
|
return handler, path_variables, false
|
|
}
|
|
|
|
}
|
|
|
|
if handler, ok := current_node.handlers[method]; ok {
|
|
return handler, path_variables, true
|
|
} else {
|
|
return handler, path_variables, false
|
|
}
|
|
}
|
|
|
|
router_add_route :: proc(
|
|
router: ^Router($Error_Type),
|
|
method: http.Method,
|
|
path: []string,
|
|
handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type),
|
|
) {
|
|
current_node := router.root_node
|
|
|
|
for segment_name in path {
|
|
node, ok := current_node.children[segment_name]
|
|
if !ok {
|
|
new_node := new(RouterNode(Error_Type))
|
|
|
|
if strings.has_prefix(segment_name, ":") {
|
|
router_node_init(router, new_node, segment_name[1:])
|
|
} else {
|
|
router_node_init(router, new_node, nil)
|
|
}
|
|
|
|
current_node.children[segment_name] = new_node
|
|
current_node = new_node
|
|
continue
|
|
}
|
|
|
|
current_node = node
|
|
}
|
|
|
|
current_node.handlers[method] = handler
|
|
}
|
|
|
|
router_remove_route :: proc(
|
|
router: ^Router($Error_Type),
|
|
method: http.Method,
|
|
path: []string,
|
|
) -> (
|
|
ok: bool,
|
|
) {
|
|
current_node := router.root_node
|
|
|
|
for segment_name in path {
|
|
node, ok := current_node.children[segment_name]
|
|
if !ok do return false
|
|
current_node = node
|
|
}
|
|
|
|
if method not_in current_node.handlers {
|
|
return false
|
|
}
|
|
|
|
delete_key(¤t_node.handlers, method)
|
|
return true
|
|
}
|