mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 05:56:07 +00:00
Custom error pages
Render `src/app/views/errors.zig` view (`index` action) when an unexpected error occurs. If the view does not exist, try rendering `public/404.html`, `public/500.html` (etc.), finally falling back to a standard basic HTML/JSON response.
This commit is contained in:
parent
0c450ff9b0
commit
1c7df8a91d
16
demo/public/404.html
Normal file
16
demo/public/404.html
Normal file
@ -0,0 +1,16 @@
|
||||
<style>
|
||||
div {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%) translateY(-50%);
|
||||
-webkit-transform: translate(-50%,-50%);
|
||||
transform: translate(-50%,-50%);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<img src="/jetzig.png">
|
||||
<h1 style="font-size: 10rem; font-family: sans-serif; color: #f7931e">404</h1>
|
||||
</div>
|
20
demo/src/app/views/errors.zig
Normal file
20
demo/src/app/views/errors.zig
Normal file
@ -0,0 +1,20 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
// Generic handler for all errors.
|
||||
// Use `jetzig.http.status_codes.get(request.status_code)` to get a value that provides string
|
||||
// versions of the error code and message for use in templates.
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
var root = try data.object();
|
||||
var error_info = try data.object();
|
||||
|
||||
const status = jetzig.http.status_codes.get(request.status_code);
|
||||
|
||||
try error_info.put("code", data.string(status.getCode()));
|
||||
try error_info.put("message", data.string(status.getMessage()));
|
||||
|
||||
try root.put("error", error_info);
|
||||
|
||||
// Render with the original error status code, or override if preferred.
|
||||
return request.render(request.status_code);
|
||||
}
|
17
demo/src/app/views/errors/index.zmpl
Normal file
17
demo/src/app/views/errors/index.zmpl
Normal file
@ -0,0 +1,17 @@
|
||||
<style>
|
||||
div {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%) translateY(-50%);
|
||||
-webkit-transform: translate(-50%,-50%);
|
||||
transform: translate(-50%,-50%);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<img src="/jetzig.png" />
|
||||
<h1 style="font-size: 6rem; font-family: sans-serif; color: #f7931e">{{.error.code}}</h1>
|
||||
<h1 style="font-size: 4rem; font-family: sans-serif; color: #39b54a">{{.error.message}}</h1>
|
||||
</div>
|
@ -40,7 +40,7 @@ pub fn getAll(self: Headers, name: []const u8) []const []const u8 {
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
pub fn getFirstValue(self: *Headers, name: []const u8) ?[]const u8 {
|
||||
pub fn getFirstValue(self: *const Headers, name: []const u8) ?[]const u8 {
|
||||
return self.get(name);
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ pub fn redirect(
|
||||
/// * `Accept` header (`application/json` or `text/html`)
|
||||
/// * `Content-Type` header (`application/json` or `text/html`)
|
||||
/// * Fall back to default: HTML
|
||||
pub fn requestFormat(self: *Request) jetzig.http.Request.Format {
|
||||
pub fn requestFormat(self: *const Request) jetzig.http.Request.Format {
|
||||
return self.extensionFormat() orelse
|
||||
self.acceptHeaderFormat() orelse
|
||||
self.contentTypeHeaderFormat() orelse
|
||||
@ -211,7 +211,7 @@ pub fn getLayout(self: *Request, route: *jetzig.views.Route) ?[]const u8 {
|
||||
|
||||
/// Shortcut for `request.headers.getFirstValue`. Returns the first matching value for a given
|
||||
/// header name or `null` if not found. Header names are case-insensitive.
|
||||
pub fn getHeader(self: *Request, key: []const u8) ?[]const u8 {
|
||||
pub fn getHeader(self: *const Request, key: []const u8) ?[]const u8 {
|
||||
return self.headers.getFirstValue(key);
|
||||
}
|
||||
|
||||
@ -389,7 +389,7 @@ pub fn mail(self: *Request, name: []const u8, mail_params: jetzig.mail.MailParam
|
||||
};
|
||||
}
|
||||
|
||||
fn extensionFormat(self: *Request) ?jetzig.http.Request.Format {
|
||||
fn extensionFormat(self: *const Request) ?jetzig.http.Request.Format {
|
||||
const extension = self.path.extension orelse return null;
|
||||
if (std.mem.eql(u8, extension, ".html")) {
|
||||
return .HTML;
|
||||
@ -400,7 +400,7 @@ fn extensionFormat(self: *Request) ?jetzig.http.Request.Format {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acceptHeaderFormat(self: *Request) ?jetzig.http.Request.Format {
|
||||
pub fn acceptHeaderFormat(self: *const Request) ?jetzig.http.Request.Format {
|
||||
const acceptHeader = self.getHeader("Accept");
|
||||
|
||||
if (acceptHeader) |item| {
|
||||
@ -411,7 +411,7 @@ pub fn acceptHeaderFormat(self: *Request) ?jetzig.http.Request.Format {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn contentTypeHeaderFormat(self: *Request) ?jetzig.http.Request.Format {
|
||||
pub fn contentTypeHeaderFormat(self: *const Request) ?jetzig.http.Request.Format {
|
||||
const acceptHeader = self.getHeader("content-type");
|
||||
|
||||
if (acceptHeader) |item| {
|
||||
@ -447,13 +447,15 @@ pub fn fmtMethod(self: *const Request, colorized: bool) []const u8 {
|
||||
/// Format a status code appropriately for the current request format.
|
||||
/// e.g. `.HTML` => `404 Not Found`
|
||||
/// `.JSON` => `{ "message": "Not Found", "status": "404" }`
|
||||
pub fn formatStatus(self: *Request, status_code: jetzig.http.StatusCode) ![]const u8 {
|
||||
pub fn formatStatus(self: *const Request, status_code: jetzig.http.StatusCode) ![]const u8 {
|
||||
const status = jetzig.http.status_codes.get(status_code);
|
||||
|
||||
return switch (self.requestFormat()) {
|
||||
.JSON => try std.json.stringifyAlloc(self.allocator, .{
|
||||
.@"error" = .{
|
||||
.message = status.getMessage(),
|
||||
.status = status.getCode(),
|
||||
.code = status.getCode(),
|
||||
},
|
||||
}, .{}),
|
||||
.HTML, .UNKNOWN => status.getFormatted(.{ .linebreak = true }),
|
||||
};
|
||||
|
@ -162,13 +162,13 @@ fn renderHTML(
|
||||
};
|
||||
return request.setResponse(rendered, .{});
|
||||
} else {
|
||||
return request.setResponse(try renderNotFound(request), .{});
|
||||
return request.setResponse(try self.renderNotFound(request), .{});
|
||||
}
|
||||
} else {
|
||||
if (try self.renderMarkdown(request)) |rendered| {
|
||||
return request.setResponse(rendered, .{});
|
||||
} else {
|
||||
return request.setResponse(try renderNotFound(request), .{});
|
||||
return request.setResponse(try self.renderNotFound(request), .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,7 +191,7 @@ fn renderJSON(
|
||||
|
||||
request.setResponse(rendered, .{});
|
||||
} else {
|
||||
request.setResponse(try renderNotFound(request), .{});
|
||||
request.setResponse(try self.renderNotFound(request), .{});
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ fn renderView(
|
||||
_ = route.render(route.*, request) catch |err| {
|
||||
try self.logger.ERROR("Encountered error: {s}", .{@errorName(err)});
|
||||
if (isUnhandledError(err)) return err;
|
||||
if (isBadRequest(err)) return try renderBadRequest(request);
|
||||
if (isBadRequest(err)) return try self.renderBadRequest(request);
|
||||
return try self.renderInternalServerError(request, err);
|
||||
};
|
||||
|
||||
@ -239,7 +239,7 @@ fn renderView(
|
||||
};
|
||||
} else {
|
||||
return switch (request.requestFormat()) {
|
||||
.HTML, .UNKNOWN => try renderNotFound(request),
|
||||
.HTML, .UNKNOWN => try self.renderNotFound(request),
|
||||
.JSON => .{ .view = rendered_view, .content = "" },
|
||||
};
|
||||
}
|
||||
@ -326,37 +326,117 @@ fn renderInternalServerError(self: *Server, request: *jetzig.http.Request, err:
|
||||
if (stack) |capture| try self.logStackTrace(capture, request);
|
||||
|
||||
const status = .internal_server_error;
|
||||
const content = try request.formatStatus(status);
|
||||
return .{
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },
|
||||
.content = content,
|
||||
};
|
||||
return try self.renderError(request, status);
|
||||
}
|
||||
|
||||
fn renderNotFound(request: *jetzig.http.Request) !RenderedView {
|
||||
fn renderNotFound(self: *Server, request: *jetzig.http.Request) !RenderedView {
|
||||
request.response_data.reset();
|
||||
|
||||
const status: jetzig.http.StatusCode = .not_found;
|
||||
const content = try request.formatStatus(status);
|
||||
return try self.renderError(request, status);
|
||||
}
|
||||
|
||||
fn renderBadRequest(self: *Server, request: *jetzig.http.Request) !RenderedView {
|
||||
request.response_data.reset();
|
||||
|
||||
const status: jetzig.http.StatusCode = .bad_request;
|
||||
return try self.renderError(request, status);
|
||||
}
|
||||
|
||||
fn renderError(
|
||||
self: Server,
|
||||
request: *jetzig.http.Request,
|
||||
status_code: jetzig.http.StatusCode,
|
||||
) !RenderedView {
|
||||
if (try self.renderErrorView(request, status_code)) |view| return view;
|
||||
if (try renderStaticErrorPage(request, status_code)) |view| return view;
|
||||
|
||||
return try renderDefaultError(request, status_code);
|
||||
}
|
||||
|
||||
fn renderErrorView(
|
||||
self: Server,
|
||||
request: *jetzig.http.Request,
|
||||
status_code: jetzig.http.StatusCode,
|
||||
) !?RenderedView {
|
||||
for (self.routes) |route| {
|
||||
if (std.mem.eql(u8, route.view_name, "errors") and route.action == .index) {
|
||||
request.response_data.reset();
|
||||
request.status_code = status_code;
|
||||
|
||||
_ = route.render(route.*, request) catch |err| {
|
||||
if (isUnhandledError(err)) return err;
|
||||
try self.logger.ERROR(
|
||||
"Unexepected error occurred while rendering error page: {s}",
|
||||
.{@errorName(err)},
|
||||
);
|
||||
const stack = @errorReturnTrace();
|
||||
if (stack) |capture| try self.logStackTrace(capture, request);
|
||||
return try renderDefaultError(request, status_code);
|
||||
};
|
||||
|
||||
if (request.rendered_view) |view| {
|
||||
switch (request.requestFormat()) {
|
||||
.HTML, .UNKNOWN => {
|
||||
if (zmpl.findPrefixed("views", route.template)) |template| {
|
||||
try addTemplateConstants(view, route);
|
||||
return .{ .view = view, .content = try template.render(request.response_data) };
|
||||
}
|
||||
},
|
||||
.JSON => return .{ .view = view, .content = try request.response_data.toJson() },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn renderStaticErrorPage(request: *jetzig.http.Request, status_code: jetzig.http.StatusCode) !?RenderedView {
|
||||
if (request.requestFormat() == .JSON) return null;
|
||||
|
||||
var dir = std.fs.cwd().openDir(
|
||||
jetzig.config.get([]const u8, "public_content_path"),
|
||||
.{ .iterate = false, .no_follow = true },
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
defer dir.close();
|
||||
|
||||
const status = jetzig.http.status_codes.get(status_code);
|
||||
const content = dir.readFileAlloc(
|
||||
request.allocator,
|
||||
try std.mem.concat(request.allocator, u8, &.{ status.getCode(), ".html" }),
|
||||
jetzig.config.get(usize, "max_bytes_public_content"),
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.view = .{ .data = request.response_data, .status_code = status },
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = status_code },
|
||||
.content = content,
|
||||
};
|
||||
}
|
||||
|
||||
fn renderBadRequest(request: *jetzig.http.Request) !RenderedView {
|
||||
request.response_data.reset();
|
||||
|
||||
const status: jetzig.http.StatusCode = .bad_request;
|
||||
const content = try request.formatStatus(status);
|
||||
fn renderDefaultError(
|
||||
request: *const jetzig.http.Request,
|
||||
status_code: jetzig.http.StatusCode,
|
||||
) !RenderedView {
|
||||
const content = try request.formatStatus(status_code);
|
||||
return .{
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = status_code },
|
||||
.content = content,
|
||||
};
|
||||
}
|
||||
|
||||
fn logStackTrace(
|
||||
self: *Server,
|
||||
self: Server,
|
||||
stack: *std.builtin.StackTrace,
|
||||
request: *jetzig.http.Request,
|
||||
) !void {
|
||||
|
Loading…
x
Reference in New Issue
Block a user