#+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 }