mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-07-01 13:36:08 +00:00
Refactor rendering
Render appropriately-formatted errors for HTML/JSON.
This commit is contained in:
parent
625257bcfc
commit
95a8330629
@ -129,7 +129,9 @@ fn renderMarkdown(
|
||||
jetzig_options.markdown_fragments
|
||||
else
|
||||
null;
|
||||
const content = try jetzig.markdown.render(allocator, &route, fragments) orelse return null;
|
||||
const path = try std.mem.join(allocator, "/", &[_][]const u8{ route.uri_path, @tagName(route.action) });
|
||||
defer allocator.free(path);
|
||||
const content = try jetzig.markdown.render(allocator, path, fragments) orelse return null;
|
||||
|
||||
if (route.layout) |layout_name| {
|
||||
try view.data.addConst("jetzig_view", view.data.string(route.name));
|
||||
|
@ -10,5 +10,6 @@ pub const Headers = @import("http/Headers.zig");
|
||||
pub const Query = @import("http/Query.zig");
|
||||
pub const Path = @import("http/Path.zig");
|
||||
pub const status_codes = @import("http/status_codes.zig");
|
||||
pub const StatusCode = status_codes.StatusCode;
|
||||
pub const middleware = @import("http/middleware.zig");
|
||||
pub const mime = @import("http/mime.zig");
|
||||
|
@ -327,6 +327,34 @@ pub fn fmtMethod(self: *const Self, 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: *Self, 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, .{
|
||||
.message = status.getMessage(),
|
||||
.status = status.getCode(),
|
||||
}, .{}),
|
||||
.HTML, .UNKNOWN => status.getFormatted(.{ .linebreak = true }),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setResponse(
|
||||
self: *Self,
|
||||
rendered_view: jetzig.http.Server.RenderedView,
|
||||
options: struct { content_type: ?[]const u8 = null },
|
||||
) void {
|
||||
self.response.content = rendered_view.content;
|
||||
self.response.status_code = rendered_view.view.status_code;
|
||||
self.response.content_type = options.content_type orelse switch (self.requestFormat()) {
|
||||
.HTML, .UNKNOWN => "text/html",
|
||||
.JSON => "application/json",
|
||||
};
|
||||
}
|
||||
|
||||
// Determine if a given route matches the current request.
|
||||
pub fn match(self: *Self, route: jetzig.views.Route) !bool {
|
||||
return switch (self.method) {
|
||||
|
@ -108,12 +108,12 @@ fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
|
||||
if (isUnhandledError(err)) return err;
|
||||
|
||||
const rendered = try self.renderInternalServerError(request, err);
|
||||
setResponse(request, rendered, .{});
|
||||
request.setResponse(rendered, .{});
|
||||
return;
|
||||
};
|
||||
|
||||
if (static_resource) |resource| {
|
||||
try renderStatic(resource, request.response);
|
||||
try renderStatic(resource, request);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -126,20 +126,11 @@ fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn setResponse(
|
||||
request: *jetzig.http.Request,
|
||||
rendered_view: RenderedView,
|
||||
options: struct { content_type: []const u8 = "text/html" },
|
||||
) void {
|
||||
request.response.content = rendered_view.content;
|
||||
request.response.status_code = rendered_view.view.status_code;
|
||||
request.response.content_type = options.content_type;
|
||||
}
|
||||
|
||||
fn renderStatic(resource: StaticResource, response: *jetzig.http.Response) !void {
|
||||
response.status_code = .ok;
|
||||
response.content = resource.content;
|
||||
response.content_type = resource.mime_type;
|
||||
fn renderStatic(resource: StaticResource, request: *jetzig.http.Request) !void {
|
||||
request.setResponse(
|
||||
.{ .view = .{ .data = request.response_data }, .content = resource.content },
|
||||
.{ .content_type = resource.mime_type },
|
||||
);
|
||||
}
|
||||
|
||||
fn renderHTML(
|
||||
@ -152,19 +143,17 @@ fn renderHTML(
|
||||
const rendered = self.renderView(matched_route, request, template) catch |err| {
|
||||
if (isUnhandledError(err)) return err;
|
||||
const rendered_error = try self.renderInternalServerError(request, err);
|
||||
setResponse(request, rendered_error, .{});
|
||||
return;
|
||||
return request.setResponse(rendered_error, .{});
|
||||
};
|
||||
setResponse(request, rendered, .{});
|
||||
return;
|
||||
return request.setResponse(rendered, .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (try self.renderMarkdown(request, route)) return;
|
||||
|
||||
request.response.content = "";
|
||||
request.response.status_code = .not_found;
|
||||
request.response.content_type = "text/html";
|
||||
if (try self.renderMarkdown(request, route)) |rendered| {
|
||||
return request.setResponse(rendered, .{});
|
||||
} else {
|
||||
return request.setResponse(try renderNotFound(request), .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn renderJSON(
|
||||
@ -173,34 +162,34 @@ fn renderJSON(
|
||||
route: ?*jetzig.views.Route,
|
||||
) !void {
|
||||
if (route) |matched_route| {
|
||||
const rendered = try self.renderView(matched_route, request, null);
|
||||
var rendered = try self.renderView(matched_route, request, null);
|
||||
var data = rendered.view.data;
|
||||
|
||||
if (data.value) |_| {} else _ = try data.object();
|
||||
try request.headers.append("Content-Type", "application/json");
|
||||
|
||||
request.response.content = try data.toJson();
|
||||
request.response.status_code = rendered.view.status_code;
|
||||
request.response.content_type = "application/json";
|
||||
rendered.content = try data.toJson();
|
||||
request.setResponse(rendered, .{});
|
||||
} else {
|
||||
request.response.content = "";
|
||||
request.response.status_code = .not_found;
|
||||
request.response.content_type = "application/json";
|
||||
request.setResponse(try renderNotFound(request), .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn renderMarkdown(self: *Self, request: *jetzig.http.Request, maybe_route: ?*jetzig.views.Route) !bool {
|
||||
fn renderMarkdown(
|
||||
self: *Self,
|
||||
request: *jetzig.http.Request,
|
||||
maybe_route: ?*jetzig.views.Route,
|
||||
) !?RenderedView {
|
||||
const route = maybe_route orelse {
|
||||
if (request.method != .GET) return false;
|
||||
const content = try jetzig.markdown.render(request.allocator, request.path.base_path, null) orelse
|
||||
return false;
|
||||
|
||||
const rendered: RenderedView = .{
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = .ok },
|
||||
.content = content,
|
||||
};
|
||||
setResponse(request, rendered, .{});
|
||||
return true;
|
||||
// No route recognized, but we can still render a static markdown file if it matches the URI:
|
||||
if (request.method != .GET) return null;
|
||||
if (try jetzig.markdown.render(request.allocator, request.path.base_path, null)) |content| {
|
||||
return .{
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = .ok },
|
||||
.content = content,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const path = try std.mem.join(
|
||||
@ -209,13 +198,11 @@ fn renderMarkdown(self: *Self, request: *jetzig.http.Request, maybe_route: ?*jet
|
||||
&[_][]const u8{ route.uri_path, @tagName(route.action) },
|
||||
);
|
||||
const markdown_content = try jetzig.markdown.render(request.allocator, path, null) orelse
|
||||
return false;
|
||||
return null;
|
||||
|
||||
const rendered = self.renderView(route, request, null) catch |err| {
|
||||
var rendered = self.renderView(route, request, null) catch |err| {
|
||||
if (isUnhandledError(err)) return err;
|
||||
const rendered_error = try self.renderInternalServerError(request, err);
|
||||
setResponse(request, rendered_error, .{});
|
||||
return true;
|
||||
return try self.renderInternalServerError(request, err);
|
||||
};
|
||||
|
||||
try addTemplateConstants(rendered.view, route);
|
||||
@ -231,18 +218,16 @@ fn renderMarkdown(self: *Self, request: *jetzig.http.Request, maybe_route: ?*jet
|
||||
|
||||
if (zmpl.manifest.find(prefixed_name)) |layout| {
|
||||
rendered.view.data.content = .{ .data = markdown_content };
|
||||
request.response.content = try layout.render(rendered.view.data);
|
||||
rendered.content = try layout.render(rendered.view.data);
|
||||
} else {
|
||||
try self.logger.WARN("Unknown layout: {s}", .{layout_name});
|
||||
request.response.content = markdown_content;
|
||||
rendered.content = markdown_content;
|
||||
}
|
||||
}
|
||||
request.response.status_code = rendered.view.status_code;
|
||||
request.response.content_type = "text/html";
|
||||
return true;
|
||||
return rendered;
|
||||
}
|
||||
|
||||
const RenderedView = struct { view: jetzig.views.View, content: []const u8 };
|
||||
pub const RenderedView = struct { view: jetzig.views.View, content: []const u8 };
|
||||
|
||||
fn renderView(
|
||||
self: *Self,
|
||||
@ -256,7 +241,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 self.renderBadRequest(request);
|
||||
if (isBadRequest(err)) return try renderBadRequest(request);
|
||||
return try self.renderInternalServerError(request, err);
|
||||
};
|
||||
|
||||
@ -347,28 +332,38 @@ fn isBadHttpError(err: anyerror) bool {
|
||||
fn renderInternalServerError(self: *Self, request: *jetzig.http.Request, err: anyerror) !RenderedView {
|
||||
request.response_data.reset();
|
||||
|
||||
var object = try request.response_data.object();
|
||||
try object.put("error", request.response_data.string(@errorName(err)));
|
||||
try self.logger.ERROR("Encountered Error: {s}", .{@errorName(err)});
|
||||
|
||||
const stack = @errorReturnTrace();
|
||||
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 = .internal_server_error },
|
||||
.content = "Internal Server Error\n",
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },
|
||||
.content = content,
|
||||
};
|
||||
}
|
||||
|
||||
fn renderBadRequest(self: *Self, request: *jetzig.http.Request) !RenderedView {
|
||||
_ = self;
|
||||
fn renderNotFound(request: *jetzig.http.Request) !RenderedView {
|
||||
request.response_data.reset();
|
||||
|
||||
var object = try request.response_data.object();
|
||||
try object.put("error", request.response_data.string("Bad Request"));
|
||||
|
||||
const status: jetzig.http.StatusCode = .not_found;
|
||||
const content = try request.formatStatus(status);
|
||||
return .{
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = .bad_request },
|
||||
.content = "Bad Request\n",
|
||||
.view = .{ .data = request.response_data, .status_code = status },
|
||||
.content = content,
|
||||
};
|
||||
}
|
||||
|
||||
fn renderBadRequest(request: *jetzig.http.Request) !RenderedView {
|
||||
request.response_data.reset();
|
||||
|
||||
const status: jetzig.http.StatusCode = .not_found;
|
||||
const content = try request.formatStatus(status);
|
||||
return .{
|
||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },
|
||||
.content = content,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
@ -66,6 +67,8 @@ pub const StatusCode = enum {
|
||||
network_authentication_required,
|
||||
};
|
||||
|
||||
const FormatOptions = struct { colorized: bool = false, linebreak: bool = false };
|
||||
|
||||
pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) type {
|
||||
return struct {
|
||||
code: []const u8 = code,
|
||||
@ -73,11 +76,15 @@ pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) t
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn format(self: Self, colorized: bool) []const u8 {
|
||||
pub fn getFormatted(self: Self, comptime options: FormatOptions) []const u8 {
|
||||
_ = self;
|
||||
const full_message = code ++ " " ++ message;
|
||||
const linebreak = switch (builtin.os.tag) {
|
||||
.windows => "\r\n",
|
||||
inline else => "\n",
|
||||
};
|
||||
const full_message = code ++ " " ++ message ++ if (options.linebreak) linebreak else "";
|
||||
|
||||
if (!colorized) return full_message;
|
||||
if (!options.colorized) return full_message;
|
||||
|
||||
if (std.mem.startsWith(u8, code, "2")) {
|
||||
return jetzig.colors.green(full_message);
|
||||
@ -159,9 +166,9 @@ pub const TaggedStatusCode = union(StatusCode) {
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn format(self: Self, colorized: bool) []const u8 {
|
||||
pub fn getFormatted(self: Self, comptime options: FormatOptions) []const u8 {
|
||||
return switch (self) {
|
||||
inline else => |capture| capture.format(colorized),
|
||||
inline else => |capture| capture.getFormatted(options),
|
||||
};
|
||||
}
|
||||
|
||||
@ -170,4 +177,16 @@ pub const TaggedStatusCode = union(StatusCode) {
|
||||
inline else => |capture| capture.code,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getMessage(self: Self) []const u8 {
|
||||
return switch (self) {
|
||||
inline else => |capture| capture.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn get(code: StatusCode) TaggedStatusCode {
|
||||
switch (code) {
|
||||
inline else => |capture| return @unionInit(TaggedStatusCode, @tagName(capture), .{}),
|
||||
}
|
||||
}
|
||||
|
@ -83,10 +83,15 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
||||
),
|
||||
};
|
||||
|
||||
const formatted_status = if (self.stdout_colorized)
|
||||
status.getFormatted(.{ .colorized = true })
|
||||
else
|
||||
status.getFormatted(.{});
|
||||
|
||||
const message = try std.fmt.allocPrint(self.allocator, "[{s}/{s}/{s}] {s}", .{
|
||||
formatted_duration,
|
||||
request.fmtMethod(self.stdout_colorized),
|
||||
status.format(self.stdout_colorized),
|
||||
formatted_status,
|
||||
request.path.path,
|
||||
});
|
||||
defer self.allocator.free(message);
|
||||
|
@ -5,7 +5,7 @@ const Self = @This();
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
data: *jetzig.data.Data,
|
||||
status_code: jetzig.http.status_codes.StatusCode,
|
||||
status_code: jetzig.http.status_codes.StatusCode = .ok,
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
_ = self;
|
||||
|
Loading…
x
Reference in New Issue
Block a user