fjord/router.odin
2025-11-09 11:11:15 +01:00

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(&current_node.handlers, method)
return true
}