zouter/src/router.zig

195 lines
6.1 KiB
Zig

const std = @import("std");
const zap = @import("zap");
const routeManager = @import("route.zig");
/// Matched route structure.
pub const MatchedRoute = struct {
route: ?*routeManager.RouteNode = null,
params: routeManager.RouteParamsMap = undefined,
};
/// Route handler function.
pub const RouteHandler = *const fn (route: MatchedRoute, request: zap.Request) anyerror!void;
/// Route pre-handler function.
pub const RoutePreHandler = *const fn (route: MatchedRoute, request: zap.Request) anyerror!bool;
/// Error route handler function.
pub const ErrorRouteHandler = *const fn (route: MatchedRoute, request: zap.Request, err: anyerror) anyerror!void;
/// Route handler definition for each request method.
pub const RouteHandlerDefinition = struct {
get: ?RouteHandler = null,
post: ?RouteHandler = null,
patch: ?RouteHandler = null,
put: ?RouteHandler = null,
delete: ?RouteHandler = null,
any: ?RouteHandler = null,
};
/// Router root tree definition.
pub const RouterDefinition = struct {
/// Not found function, called when a route was not found.
handleNotFound: ?RouteHandler = null,
/// Error function, called when a route encountered an error.
handleError: ?ErrorRouteHandler = null,
};
/// Route definition object.
pub const RouteDefinition = struct {
/// Route path.
path: []const u8,
/// Children routes: full path will be their path appended to the current route path.
children: ?([]const RouteDefinition) = null,
/// Handle function, called when this route is reached.
handle: ?RouteHandlerDefinition = null,
/// Not found function, called when a child route was not found.
handleNotFound: ?RouteHandler = null,
/// Error function, called when a child route encountered an error.
handleError: ?ErrorRouteHandler = null,
/// Pre-handler function, called before handling this route or any children.
preHandle: ?RoutePreHandler = null,
/// Post-handler function, called after handling this route or any children.
postHandle: ?RouteHandler = null,
};
/// A router structure.
pub const Router = struct {
const Self = @This();
/// Internal static router instance.
var routerInstance: Self = undefined;
/// Root of the route tree.
root: routeManager.RouteNode,
/// Initialize a new router instance.
pub fn init(allocator: std.mem.Allocator, definition: RouterDefinition) !Self
{
routerInstance = Self{
.root = try routeManager.RouteNode.init(allocator, ""),
};
// Handle of the root tree is never used.
routerInstance.root.handle = .{
.any = &impossible,
};
routerInstance.root.handleNotFound = definition.handleNotFound orelse &defaultNotFoundHandler;
routerInstance.root.handleError = definition.handleError orelse &defaultErrorHandler;
return routerInstance;
}
/// Deinitialize the router instance.
pub fn deinit(self: *Self) void
{
self.root.deinit();
routerInstance = undefined;
}
/// Handle an error which happen in any handler.
fn handleError(request: zap.Request, err: anyerror, routingResult: *routeManager.RoutingResult) void
{
// Run error handlers from the most specific to the least specific (reverse order of the array).
var errorHandlersIterator = std.mem.reverseIterator(routingResult.errorHandlers.items);
while (errorHandlersIterator.next()) |errorHandler|
{ // For each error handler, try to run it with the given error.
errorHandler(.{
.route = routingResult.route,
.params = routingResult.params,
}, request, err) catch {
// Error handler failed, we try the next one.
continue;
};
return; // Error handler ran successfully, we can stop there.
}
}
/// Handle an incoming request and call the right route.
fn handle(self: *Self, request: zap.Request) void
{
// Split path in route parts.
var path = std.mem.splitScalar(u8, std.mem.trim(u8, request.path.?, " /"), '/');
// Try to match a route from its parts.
const routingResult = self.root.newRoutingResult() catch |err| {
// Run default error handler if something happens while building routing result.
self.root.handleError.?(.{}, request, err) catch {};
return;
};
// Matching the requested route. Put the result in routingResult pointer.
_ = self.root.match(request.methodAsEnum(), &path, routingResult) catch |err| {
Self.handleError(request, err, routingResult);
return;
};
defer routingResult.deinit();
// Try to run matched route handling.
Self.runMatchedRouteHandling(routingResult, request)
// Handle error in request handling.
catch |err| Self.handleError(request, err, routingResult);
}
/// Run a matched route.
fn runMatchedRouteHandling(routingResult: *routeManager.RoutingResult, request: zap.Request) !void
{
// Initialized route data passed to handlers from the routing result.
const routeData = .{
.route = routingResult.route,
.params = routingResult.params,
};
for (routingResult.preHandlers.items) |preHandle|
{ // Run each pre-handler. If a pre-handler returns false, handling must stop now.
if (!try preHandle(routeData, request))
return;
}
// Run matched route handler with result params.
try routingResult.handler(routeData, request);
for (routingResult.postHandlers.items) |postHandle|
{ // Run each post-handler.
try postHandle(routeData, request);
}
}
/// Define a root route.
pub fn route(self: *Self, definition: RouteDefinition) !void
{
try self.root.parse(definition);
}
/// The on_request function of the HTTP listener.
pub fn onRequest(request: zap.Request) void
{
// Call handle of the current router instance.
routerInstance.handle(request);
}
};
/// Impossible function.
fn impossible(_: MatchedRoute, _: zap.Request) !void
{
unreachable;
}
/// Default not found handling.
fn defaultNotFoundHandler(_: MatchedRoute, request: zap.Request) !void
{
try request.setContentType(zap.ContentType.TEXT);
request.setStatus(zap.StatusCode.not_found);
try request.sendBody("404: Not Found");
}
/// Default error handling.
fn defaultErrorHandler(_: MatchedRoute, request: zap.Request, _: anyerror) !void
{
try request.setContentType(zap.ContentType.TEXT);
request.setStatus(zap.StatusCode.internal_server_error);
try request.sendBody("500: Internal Server Error");
}