mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-15 06:26:07 +00:00
Error handling
This commit is contained in:
parent
65bb626bbc
commit
cb52ccffb9
@ -18,10 +18,13 @@ If you are interested in _Jetzig_ you will probably find these tools interesting
|
|||||||
* :white_check_mark: _JSON_-compatible response data builder.
|
* :white_check_mark: _JSON_-compatible response data builder.
|
||||||
* :white_check_mark: _HTML_ templating (see [Zmpl](https://github.com/bobf/zmpl)).
|
* :white_check_mark: _HTML_ templating (see [Zmpl](https://github.com/bobf/zmpl)).
|
||||||
* :white_check_mark: Per-request arena allocator.
|
* :white_check_mark: Per-request arena allocator.
|
||||||
* :x: Sessions.
|
* :white_check_mark: Sessions.
|
||||||
* :x: Cookies.
|
* :white_check_mark: Cookies.
|
||||||
* :x: Headers.
|
* :x: Error handling.
|
||||||
|
* :x: Headers (available but not yet wrapped).
|
||||||
|
* :x: Param/JSON payload parsing/abstracting.
|
||||||
* :x: Development-mode responses for debugging.
|
* :x: Development-mode responses for debugging.
|
||||||
|
* :x: Environment configurations (develompent/production/etc.)
|
||||||
* :x: Middleware extensions (for e.g. authentication).
|
* :x: Middleware extensions (for e.g. authentication).
|
||||||
* :x: Email delivery.
|
* :x: Email delivery.
|
||||||
* :x: Custom/dynamic routes.
|
* :x: Custom/dynamic routes.
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
// the new URL.
|
// the new URL.
|
||||||
//
|
//
|
||||||
.url = "https://github.com/bobf/zmpl/archive/refs/tags/0.0.1.tar.gz",
|
.url = "https://github.com/bobf/zmpl/archive/refs/tags/0.0.1.tar.gz",
|
||||||
.hash = "12205f95df0bf7a66c9d00ed76f8c3b1548eb958f9210425045c77f88ea165f20fef",
|
.hash = "12204256376f262a58935d66a2a0b41ac0447299b7e63a4c6ff160ddcef6572cd3c7",
|
||||||
|
|
||||||
// This is computed from the file contents of the directory of files that is
|
// This is computed from the file contents of the directory of files that is
|
||||||
// obtained after fetching `url` and applying the inclusion rules given by
|
// obtained after fetching `url` and applying the inclusion rules given by
|
||||||
|
@ -6,7 +6,9 @@ const Data = jetzig.data.Data;
|
|||||||
const View = jetzig.views.View;
|
const View = jetzig.views.View;
|
||||||
|
|
||||||
pub fn index(request: *Request, data: *Data) anyerror!View {
|
pub fn index(request: *Request, data: *Data) anyerror!View {
|
||||||
|
_ = request;
|
||||||
var object = try data.object();
|
var object = try data.object();
|
||||||
try object.put("foo", data.string("hello"));
|
try object.put("foo", data.string("hello"));
|
||||||
return request.render(.ok);
|
return error.OhNo;
|
||||||
|
// return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,12 @@ pub fn put(id: []const u8, request: *Request, data: *Data) anyerror!View {
|
|||||||
|
|
||||||
const count = try request.session.get("count");
|
const count = try request.session.get("count");
|
||||||
if (count) |value| {
|
if (count) |value| {
|
||||||
|
if (value == .integer) {
|
||||||
try request.session.put("count", data.integer(value.integer.value + 1));
|
try request.session.put("count", data.integer(value.integer.value + 1));
|
||||||
try object.put("count", data.integer(value.integer.value + 1));
|
try object.put("count", data.integer(value.integer.value + 1));
|
||||||
|
} else {
|
||||||
|
return error.InvalidSessionData;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
try request.session.put("count", data.integer(0));
|
try request.session.put("count", data.integer(0));
|
||||||
try object.put("count", data.integer(0));
|
try object.put("count", data.integer(0));
|
||||||
@ -27,10 +31,14 @@ pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View {
|
|||||||
var object = try data.object();
|
var object = try data.object();
|
||||||
const count = try request.session.get("count");
|
const count = try request.session.get("count");
|
||||||
if (count) |value| {
|
if (count) |value| {
|
||||||
|
if (value == .integer) {
|
||||||
try object.put("count", data.integer(value.integer.value + 1));
|
try object.put("count", data.integer(value.integer.value + 1));
|
||||||
} else {
|
} else {
|
||||||
try object.put("count", data.integer(0));
|
try object.put("count", data.integer(0));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try object.put("count", data.integer(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,12 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = loggers.Logger{ .development_logger = loggers.DevelopmentLogger.init(allocator) };
|
var logger = loggers.Logger{ .development_logger = loggers.DevelopmentLogger.init(allocator) };
|
||||||
const secret = try generateSecret(allocator);
|
const secret = try generateSecret(allocator);
|
||||||
|
logger.debug(
|
||||||
|
"Running in development mode, using auto-generated cookie encryption key:\n {s}",
|
||||||
|
.{secret},
|
||||||
|
);
|
||||||
|
|
||||||
const server_options = http.Server.ServerOptions{
|
const server_options = http.Server.ServerOptions{
|
||||||
.cache = server_cache,
|
.cache = server_cache,
|
||||||
|
@ -68,8 +68,8 @@ fn processRequests(self: *Self) !void {
|
|||||||
while (response.reset() != .closing) {
|
while (response.reset() != .closing) {
|
||||||
self.processNextRequest(&response) catch |err| {
|
self.processNextRequest(&response) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.EndOfStream => continue,
|
error.EndOfStream, error.ConnectionResetByPeer => continue,
|
||||||
error.ConnectionResetByPeer => continue,
|
error.UnknownHttpMethod => continue, // TODO: Render 400 Bad Request here ?
|
||||||
else => return err,
|
else => return err,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -100,8 +100,7 @@ fn processNextRequest(self: *Self, response: *std.http.Server.Response) !void {
|
|||||||
try response.headers.append("Set-Cookie", header);
|
try response.headers.append("Set-Cookie", header);
|
||||||
}
|
}
|
||||||
response.status = switch (result.value.status_code) {
|
response.status = switch (result.value.status_code) {
|
||||||
.ok => .ok,
|
inline else => |status_code| @field(std.http.Status, @tagName(status_code)),
|
||||||
.not_found => .not_found,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try response.do();
|
try response.do();
|
||||||
@ -148,9 +147,12 @@ fn renderHTML(
|
|||||||
// FIXME: Tidy this up and use a hashmap for templates (or a more comprehensive
|
// FIXME: Tidy this up and use a hashmap for templates (or a more comprehensive
|
||||||
// matching system) instead of an array.
|
// matching system) instead of an array.
|
||||||
if (std.mem.eql(u8, expected_name, template.name)) {
|
if (std.mem.eql(u8, expected_name, template.name)) {
|
||||||
const view = try matched_route.render(matched_route, request);
|
const rendered = try self.renderView(matched_route, request, template);
|
||||||
const content = try template.render(view.data);
|
return .{
|
||||||
return .{ .allocator = self.allocator, .content = content, .status_code = .ok };
|
.allocator = self.allocator,
|
||||||
|
.content = rendered.content,
|
||||||
|
.status_code = rendered.view.status_code,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,12 +176,15 @@ fn renderJSON(
|
|||||||
route: ?jetzig.views.Route,
|
route: ?jetzig.views.Route,
|
||||||
) !jetzig.http.Response {
|
) !jetzig.http.Response {
|
||||||
if (route) |matched_route| {
|
if (route) |matched_route| {
|
||||||
const view = try matched_route.render(matched_route, request);
|
const rendered = try self.renderView(matched_route, request, null);
|
||||||
var data = view.data;
|
var data = rendered.view.data;
|
||||||
|
|
||||||
|
if (data.value) |_| {} else _ = try data.object();
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.allocator = self.allocator,
|
.allocator = self.allocator,
|
||||||
.content = try data.toJson(),
|
.content = try data.toJson(),
|
||||||
.status_code = .ok,
|
.status_code = rendered.view.status_code,
|
||||||
};
|
};
|
||||||
} else return .{
|
} else return .{
|
||||||
.allocator = self.allocator,
|
.allocator = self.allocator,
|
||||||
@ -188,10 +193,42 @@ fn renderJSON(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RenderedView = struct { view: jetzig.views.View, content: []const u8 };
|
||||||
|
fn renderView(
|
||||||
|
self: *Self,
|
||||||
|
matched_route: jetzig.views.Route,
|
||||||
|
request: *jetzig.http.Request,
|
||||||
|
template: ?jetzig.TemplateFn,
|
||||||
|
) !RenderedView {
|
||||||
|
const view = matched_route.render(matched_route, request) catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.OutOfMemory => return err,
|
||||||
|
else => return try self.internalServerError(request, err),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const content = if (template) |capture| try capture.render(view.data) else "";
|
||||||
|
|
||||||
|
return .{ .view = view, .content = content };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internalServerError(self: *Self, request: *jetzig.http.Request, err: anyerror) !RenderedView {
|
||||||
|
_ = self;
|
||||||
|
request.response_data.reset();
|
||||||
|
var object = try request.response_data.object();
|
||||||
|
try object.put("error", request.response_data.string(@errorName(err)));
|
||||||
|
return .{
|
||||||
|
.view = jetzig.views.View{ .data = request.response_data, .status_code = .internal_server_error },
|
||||||
|
.content = "An unexpected error occurred.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn requestLogMessage(self: *Self, request: *jetzig.http.Request, result: jetzig.caches.Result) ![]const u8 {
|
fn requestLogMessage(self: *Self, request: *jetzig.http.Request, result: jetzig.caches.Result) ![]const u8 {
|
||||||
const status: jetzig.http.status_codes.TaggedStatusCode = switch (result.value.status_code) {
|
const status: jetzig.http.status_codes.TaggedStatusCode = switch (result.value.status_code) {
|
||||||
.ok => .{ .ok = .{} },
|
inline else => |status_code| @unionInit(
|
||||||
.not_found => .{ .not_found = .{} },
|
jetzig.http.status_codes.TaggedStatusCode,
|
||||||
|
@tagName(status_code),
|
||||||
|
.{},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatted_duration = try jetzig.colors.duration(self.allocator, self.duration());
|
const formatted_duration = try jetzig.colors.duration(self.allocator, self.duration());
|
||||||
|
@ -3,8 +3,67 @@ const std = @import("std");
|
|||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
pub const StatusCode = enum {
|
pub const StatusCode = enum {
|
||||||
|
@"continue",
|
||||||
|
switching_protocols,
|
||||||
|
processing,
|
||||||
|
early_hints,
|
||||||
ok,
|
ok,
|
||||||
|
created,
|
||||||
|
accepted,
|
||||||
|
non_authoritative_info,
|
||||||
|
no_content,
|
||||||
|
reset_content,
|
||||||
|
partial_content,
|
||||||
|
multi_status,
|
||||||
|
already_reported,
|
||||||
|
im_used,
|
||||||
|
multiple_choice,
|
||||||
|
moved_permanently,
|
||||||
|
found,
|
||||||
|
see_other,
|
||||||
|
not_modified,
|
||||||
|
use_proxy,
|
||||||
|
temporary_redirect,
|
||||||
|
permanent_redirect,
|
||||||
|
bad_request,
|
||||||
|
unauthorized,
|
||||||
|
payment_required,
|
||||||
|
forbidden,
|
||||||
not_found,
|
not_found,
|
||||||
|
method_not_allowed,
|
||||||
|
not_acceptable,
|
||||||
|
proxy_auth_required,
|
||||||
|
request_timeout,
|
||||||
|
conflict,
|
||||||
|
gone,
|
||||||
|
length_required,
|
||||||
|
precondition_failed,
|
||||||
|
payload_too_large,
|
||||||
|
uri_too_long,
|
||||||
|
unsupported_media_type,
|
||||||
|
range_not_satisfiable,
|
||||||
|
expectation_failed,
|
||||||
|
misdirected_request,
|
||||||
|
unprocessable_entity,
|
||||||
|
locked,
|
||||||
|
failed_dependency,
|
||||||
|
too_early,
|
||||||
|
upgrade_required,
|
||||||
|
precondition_required,
|
||||||
|
too_many_requests,
|
||||||
|
request_header_fields_too_large,
|
||||||
|
unavailable_for_legal_reasons,
|
||||||
|
internal_server_error,
|
||||||
|
not_implemented,
|
||||||
|
bad_gateway,
|
||||||
|
service_unavailable,
|
||||||
|
gateway_timeout,
|
||||||
|
http_version_not_supported,
|
||||||
|
variant_also_negotiates,
|
||||||
|
insufficient_storage,
|
||||||
|
loop_detected,
|
||||||
|
not_extended,
|
||||||
|
network_authentication_required,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) type {
|
pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) type {
|
||||||
@ -35,8 +94,67 @@ pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) t
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const TaggedStatusCode = union(StatusCode) {
|
pub const TaggedStatusCode = union(StatusCode) {
|
||||||
ok: StatusCodeType("200", "OK"),
|
@"continue": StatusCodeType("100", "Continue"),
|
||||||
|
switching_protocols: StatusCodeType("101", "Switching Protocols"),
|
||||||
|
processing: StatusCodeType("102", "Processing"),
|
||||||
|
early_hints: StatusCodeType("103", "Early Hints"),
|
||||||
|
ok: StatusCodeType("200", "Ok"),
|
||||||
|
created: StatusCodeType("201", "Created"),
|
||||||
|
accepted: StatusCodeType("202", "Accepted"),
|
||||||
|
non_authoritative_info: StatusCodeType("203", "Non Authoritative Information"),
|
||||||
|
no_content: StatusCodeType("204", "No Content"),
|
||||||
|
reset_content: StatusCodeType("205", "Reset Content"),
|
||||||
|
partial_content: StatusCodeType("206", "Partial Content"),
|
||||||
|
multi_status: StatusCodeType("207", "Multi Status"),
|
||||||
|
already_reported: StatusCodeType("208", "Already Reported"),
|
||||||
|
im_used: StatusCodeType("226", "IM Used"),
|
||||||
|
multiple_choice: StatusCodeType("300", "Multiple Choices"),
|
||||||
|
moved_permanently: StatusCodeType("301", "Moved Permanently"),
|
||||||
|
found: StatusCodeType("302", "Found"),
|
||||||
|
see_other: StatusCodeType("303", "See Other"),
|
||||||
|
not_modified: StatusCodeType("304", "Not Modified"),
|
||||||
|
use_proxy: StatusCodeType("305", "Use Proxy"),
|
||||||
|
temporary_redirect: StatusCodeType("307", "Temporary Redirect"),
|
||||||
|
permanent_redirect: StatusCodeType("308", "Permanent Redirect"),
|
||||||
|
bad_request: StatusCodeType("400", "Bad Request"),
|
||||||
|
unauthorized: StatusCodeType("401", "Unauthorized"),
|
||||||
|
payment_required: StatusCodeType("402", "Payment Required"),
|
||||||
|
forbidden: StatusCodeType("403", "Forbidden"),
|
||||||
not_found: StatusCodeType("404", "Not Found"),
|
not_found: StatusCodeType("404", "Not Found"),
|
||||||
|
method_not_allowed: StatusCodeType("405", "Method Not Allowed"),
|
||||||
|
not_acceptable: StatusCodeType("406", "Not Acceptable"),
|
||||||
|
proxy_auth_required: StatusCodeType("407", "Proxy Authentication Required"),
|
||||||
|
request_timeout: StatusCodeType("408", "Request Timeout"),
|
||||||
|
conflict: StatusCodeType("409", "Conflict"),
|
||||||
|
gone: StatusCodeType("410", "Gone"),
|
||||||
|
length_required: StatusCodeType("411", "Length Required"),
|
||||||
|
precondition_failed: StatusCodeType("412", "Precondition Failed"),
|
||||||
|
payload_too_large: StatusCodeType("413", "Payload Too Large"),
|
||||||
|
uri_too_long: StatusCodeType("414", "Request Uri Too Long"),
|
||||||
|
unsupported_media_type: StatusCodeType("415", "Unsupported Media Type"),
|
||||||
|
range_not_satisfiable: StatusCodeType("416", "Requested Range Not Satisfiable"),
|
||||||
|
expectation_failed: StatusCodeType("417", "Expectation Failed"),
|
||||||
|
misdirected_request: StatusCodeType("421", "Misdirected Request"),
|
||||||
|
unprocessable_entity: StatusCodeType("422", "Unprocessable Entity"),
|
||||||
|
locked: StatusCodeType("423", "Locked"),
|
||||||
|
failed_dependency: StatusCodeType("424", "Failed Dependency"),
|
||||||
|
too_early: StatusCodeType("425", "Too Early"),
|
||||||
|
upgrade_required: StatusCodeType("426", "Upgrade Required"),
|
||||||
|
precondition_required: StatusCodeType("428", "Precondition Required"),
|
||||||
|
too_many_requests: StatusCodeType("429", "Too Many Requests"),
|
||||||
|
request_header_fields_too_large: StatusCodeType("431", "Request Header Fields Too Large"),
|
||||||
|
unavailable_for_legal_reasons: StatusCodeType("451", "Unavailable for Legal Reasons"),
|
||||||
|
internal_server_error: StatusCodeType("500", "Internal Server Error"),
|
||||||
|
not_implemented: StatusCodeType("501", "Not Implemented"),
|
||||||
|
bad_gateway: StatusCodeType("502", "Bad Gateway"),
|
||||||
|
service_unavailable: StatusCodeType("503", "Service Unavailable"),
|
||||||
|
gateway_timeout: StatusCodeType("504", "Gateway Timeout"),
|
||||||
|
http_version_not_supported: StatusCodeType("505", "Http Version Not Supported"),
|
||||||
|
variant_also_negotiates: StatusCodeType("506", "Variant Also Negotiates"),
|
||||||
|
insufficient_storage: StatusCodeType("507", "Insufficient Storage"),
|
||||||
|
loop_detected: StatusCodeType("508", "Loop Detected"),
|
||||||
|
not_extended: StatusCodeType("510", "Not Extended"),
|
||||||
|
network_authentication_required: StatusCodeType("511", "Network Authentication Required"),
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user