@@ -2,6 +2,8 @@ const std = @import("std");
22const log = std .log .scoped (.@"zzz/http/router" );
33const assert = std .debug .assert ;
44
5+ const HTTPError = @import ("lib.zig" ).HTTPError ;
6+
57const _Route = @import ("router/route.zig" ).Route ;
68
79const Capture = @import ("router/routing_trie.zig" ).Capture ;
@@ -13,7 +15,13 @@ const _Context = @import("context.zig").Context;
1315const _RoutingTrie = @import ("router/routing_trie.zig" ).RoutingTrie ;
1416const QueryMap = @import ("router/routing_trie.zig" ).QueryMap ;
1517
16- /// Default not found handler: send a plain text response.
18+ /// Error handler type.
19+ pub fn ErrorHandlerFn (comptime Server : type , comptime AppState : type ) type {
20+ const Context = _Context (Server , AppState );
21+ return * const fn (context : * Context , err : anyerror ) anyerror ! void ;
22+ }
23+
24+ /// Create a default not found handler: send a plain text response.
1725pub fn default_not_found_handler (comptime Server : type , comptime AppState : type ) _Route (Server , AppState ).HandlerFn {
1826 const Context = _Context (Server , AppState );
1927
@@ -28,6 +36,87 @@ pub fn default_not_found_handler(comptime Server: type, comptime AppState: type)
2836 }.not_found_handler ;
2937}
3038
39+ /// Create a default error handler: send a plain text response with the error, if known, internal server error otherwise.
40+ pub fn default_error_handler (comptime Server : type , comptime AppState : type ) ErrorHandlerFn (Server , AppState ) {
41+ const Context = _Context (Server , AppState );
42+ return struct { fn f (ctx : * Context , err : anyerror ) ! void {
43+ // Handle all default HTTP errors.
44+ switch (err ) {
45+ HTTPError .ContentTooLarge = > {
46+ try ctx .respond (.{
47+ .status = .@"Content Too Large" ,
48+ .mime = Mime .TEXT ,
49+ .body = "Request was too large." ,
50+ });
51+ },
52+ HTTPError .HTTPVersionNotSupported = > {
53+ try ctx .respond (.{
54+ .status = .@"HTTP Version Not Supported" ,
55+ .mime = Mime .HTML ,
56+ .body = "HTTP version not supported." ,
57+ });
58+ },
59+ HTTPError .InvalidMethod = > {
60+ try ctx .respond (.{
61+ .status = .@"Not Implemented" ,
62+ .mime = Mime .TEXT ,
63+ .body = "Not implemented." ,
64+ });
65+ },
66+ HTTPError .LengthRequired = > {
67+ try ctx .respond (.{
68+ .status = .@"Length Required" ,
69+ .mime = Mime .TEXT ,
70+ .body = "Length required." ,
71+ });
72+ },
73+ HTTPError .MalformedRequest = > {
74+ try ctx .respond (.{
75+ .status = .@"Bad Request" ,
76+ .mime = Mime .TEXT ,
77+ .body = "Malformed request." ,
78+ });
79+ },
80+ HTTPError .MethodNotAllowed = > {
81+ if (ctx .route ) | route | {
82+ add_allow_header : {
83+ // We also need to add to Allow header.
84+ // This uses the connection's arena to allocate 64 bytes.
85+ const allowed = route .get_allowed (ctx .provision .arena .allocator ()) catch break :add_allow_header ;
86+ ctx .provision .response .headers .putAssumeCapacity ("Allow" , allowed );
87+ }
88+ }
89+ try ctx .respond (.{
90+ .status = .@"Method Not Allowed" ,
91+ .mime = Mime .TEXT ,
92+ .body = "Method not allowed." ,
93+ });
94+ },
95+ HTTPError .TooManyHeaders = > {
96+ try ctx .respond (.{
97+ .status = .@"Request Header Fields Too Large" ,
98+ .mime = Mime .TEXT ,
99+ .body = "Too many headers." ,
100+ });
101+ },
102+ HTTPError .URITooLong = > {
103+ try ctx .respond (.{
104+ .status = .@"URI Too Long" ,
105+ .mime = Mime .TEXT ,
106+ .body = "URI too long." ,
107+ });
108+ },
109+ else = > {
110+ try ctx .respond (.{
111+ .status = .@"Internal Server Error" ,
112+ .mime = Mime .TEXT ,
113+ .body = "Internal server error." ,
114+ });
115+ },
116+ }
117+ } }.f ;
118+ }
119+
31120/// Initialize a router with the given routes.
32121pub fn Router (comptime Server : type , comptime AppState : type ) type {
33122 return struct {
@@ -40,17 +129,20 @@ pub fn Router(comptime Server: type, comptime AppState: type) type {
40129 /// Router configuration structure.
41130 pub const Configuration = struct {
42131 not_found_handler : Route.HandlerFn = default_not_found_handler (Server , AppState ),
132+ error_handler : ErrorHandlerFn (Server , AppState ) = default_error_handler (Server , AppState ),
43133 };
44134
45135 routes : RoutingTrie ,
46136 not_found_route : Route ,
137+ error_handler : ErrorHandlerFn (Server , AppState ),
47138 state : AppState ,
48139
49140 pub fn init (state : AppState , comptime _routes : []const Route , comptime configuration : Configuration ) Self {
50141 const self = Self {
51142 // Initialize the routing tree from the given routes.
52143 .routes = comptime RoutingTrie .init (_routes ),
53144 .not_found_route = comptime Route .init ("" ).all (configuration .not_found_handler ),
145+ .error_handler = configuration .error_handler ,
54146 .state = state ,
55147 };
56148
0 commit comments