Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to set a custom error handler in router configuration. #21

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ pub fn main() !void {
});
}
}.handler_fn,
.error_handler = struct {
fn handler_fn(ctx: *Context, _: anyerror) !void {
try ctx.respond(.{
.status = .@"Internal Server Error",
.mime = http.Mime.HTML,
.body = "Oh no, Internal Server Error!",
});
}
}.handler_fn,
});

// This provides the entry function into the Tardy runtime. This will run
Expand Down
9 changes: 9 additions & 0 deletions examples/basic/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ pub fn main() !void {
});
}
}.handler_fn,
.error_handler = struct {
fn handler_fn(ctx: *Context, _: anyerror) !void {
try ctx.respond(.{
.status = .@"Internal Server Error",
.mime = http.Mime.HTML,
.body = "Oh no, Internal Server Error!",
});
}
}.handler_fn,
});

// This provides the entry function into the Tardy runtime. This will run
Expand Down
4 changes: 1 addition & 3 deletions src/http/context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ const _SSE = @import("sse.zig").SSE;
const Runtime = @import("tardy").Runtime;
const TaskFn = @import("tardy").TaskFn;

const raw_respond = @import("server.zig").raw_respond;

// Context is dependent on the server that gets created.
pub fn Context(comptime Server: type, comptime AppState: type) type {
return struct {
Expand All @@ -30,7 +28,7 @@ pub fn Context(comptime Server: type, comptime AppState: type) type {
/// Custom user-data state.
state: AppState,
/// The matched route instance.
route: *const Route(Server, AppState),
route: ?*const Route(Server, AppState),
/// The Request that triggered this handler.
request: *const Request,
/// The Response that will be returned.
Expand Down
8 changes: 5 additions & 3 deletions src/http/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ pub const Headers = @import("../core/case_string_map.zig").CaseStringMap([]const
pub const Server = @import("server.zig").Server;

pub const HTTPError = error{
TooManyHeaders,
ContentTooLarge,
MalformedRequest,
HTTPVersionNotSupported,
InvalidMethod,
LengthRequired,
MalformedRequest,
MethodNotAllowed,
TooManyHeaders,
URITooLong,
HTTPVersionNotSupported,
};
94 changes: 93 additions & 1 deletion src/http/router.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const std = @import("std");
const log = std.log.scoped(.@"zzz/http/router");
const assert = std.debug.assert;

const HTTPError = @import("lib.zig").HTTPError;

const _Route = @import("router/route.zig").Route;

const Capture = @import("router/routing_trie.zig").Capture;
Expand All @@ -13,7 +15,13 @@ const _Context = @import("context.zig").Context;
const _RoutingTrie = @import("router/routing_trie.zig").RoutingTrie;
const QueryMap = @import("router/routing_trie.zig").QueryMap;

/// Default not found handler: send a plain text response.
/// Error handler type.
pub fn ErrorHandlerFn(comptime Server: type, comptime AppState: type) type {
const Context = _Context(Server, AppState);
return *const fn (context: *Context, err: anyerror) anyerror!void;
}

/// Create a default not found handler: send a plain text response.
pub fn default_not_found_handler(comptime Server: type, comptime AppState: type) _Route(Server, AppState).HandlerFn {
const Context = _Context(Server, AppState);

Expand All @@ -28,6 +36,87 @@ pub fn default_not_found_handler(comptime Server: type, comptime AppState: type)
}.not_found_handler;
}

/// Create a default error handler: send a plain text response with the error, if known, internal server error otherwise.
pub fn default_error_handler(comptime Server: type, comptime AppState: type) ErrorHandlerFn(Server, AppState) {
const Context = _Context(Server, AppState);
return struct { fn f(ctx: *Context, err: anyerror) !void {
// Handle all default HTTP errors.
switch (err) {
HTTPError.ContentTooLarge => {
try ctx.respond(.{
.status = .@"Content Too Large",
.mime = Mime.TEXT,
.body = "Request was too large.",
});
},
HTTPError.HTTPVersionNotSupported => {
try ctx.respond(.{
.status = .@"HTTP Version Not Supported",
.mime = Mime.HTML,
.body = "HTTP version not supported.",
});
},
HTTPError.InvalidMethod => {
try ctx.respond(.{
.status = .@"Not Implemented",
.mime = Mime.TEXT,
.body = "Not implemented.",
});
},
HTTPError.LengthRequired => {
try ctx.respond(.{
.status = .@"Length Required",
.mime = Mime.TEXT,
.body = "Length required.",
});
},
HTTPError.MalformedRequest => {
try ctx.respond(.{
.status = .@"Bad Request",
.mime = Mime.TEXT,
.body = "Malformed request.",
});
},
HTTPError.MethodNotAllowed => {
if (ctx.route) |route| {
add_allow_header: {
// We also need to add to Allow header.
// This uses the connection's arena to allocate 64 bytes.
const allowed = route.get_allowed(ctx.provision.arena.allocator()) catch break :add_allow_header;
ctx.provision.response.headers.put_assume_capacity("Allow", allowed);
}
}
try ctx.respond(.{
.status = .@"Method Not Allowed",
.mime = Mime.TEXT,
.body = "Method not allowed.",
});
},
HTTPError.TooManyHeaders => {
try ctx.respond(.{
.status = .@"Request Header Fields Too Large",
.mime = Mime.TEXT,
.body = "Too many headers.",
});
},
HTTPError.URITooLong => {
try ctx.respond(.{
.status = .@"URI Too Long",
.mime = Mime.TEXT,
.body = "URI too long.",
});
},
else => {
try ctx.respond(.{
.status = .@"Internal Server Error",
.mime = Mime.TEXT,
.body = "Internal server error.",
});
},
}
} }.f;
}

/// Initialize a router with the given routes.
pub fn Router(comptime Server: type, comptime AppState: type) type {
return struct {
Expand All @@ -40,17 +129,20 @@ pub fn Router(comptime Server: type, comptime AppState: type) type {
/// Router configuration structure.
pub const Configuration = struct {
not_found_handler: Route.HandlerFn = default_not_found_handler(Server, AppState),
error_handler: ErrorHandlerFn(Server, AppState) = default_error_handler(Server, AppState),
};

routes: RoutingTrie,
not_found_route: Route,
error_handler: ErrorHandlerFn(Server, AppState),
state: AppState,

pub fn init(state: AppState, comptime _routes: []const Route, comptime configuration: Configuration) Self {
const self = Self{
// Initialize the routing tree from the given routes.
.routes = comptime RoutingTrie.init(_routes),
.not_found_route = comptime Route.init("").all(configuration.not_found_handler),
.error_handler = configuration.error_handler,
.state = state,
};

Expand Down
Loading
Loading