Implement request.renderTemplate

Clean up request state.
This commit is contained in:
Bob Farrell 2024-11-23 13:42:14 +00:00
parent 036aec1682
commit 8095bbcb76
7 changed files with 57 additions and 24 deletions

View File

@ -0,0 +1,15 @@
const std = @import("std");
const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request) !jetzig.View {
return request.renderTemplate("basic/index", .ok);
}
test "index" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.GET, "/render_template", .{});
try response.expectStatus(.ok);
try response.expectBodyContains("Hello");
}

View File

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View File

@ -30,16 +30,11 @@ parsed_multipart: ?*jetzig.data.Data = null,
_cookies: ?*jetzig.http.Cookies = null, _cookies: ?*jetzig.http.Cookies = null,
_session: ?*jetzig.http.Session = null, _session: ?*jetzig.http.Session = null,
body: []const u8 = undefined, body: []const u8 = undefined,
state: enum { initial, processed } = .initial, state: enum { initial, rendered, redirected, failed, processed } = .initial,
response_started: bool = false, response_started: bool = false,
dynamic_assigned_template: ?[]const u8 = null, dynamic_assigned_template: ?[]const u8 = null,
layout: ?[]const u8 = null, layout: ?[]const u8 = null,
layout_disabled: bool = false, layout_disabled: bool = false,
// TODO: Squash rendered/redirected/failed into
// `state: enum { initial, rendered, redirected, failed }`
rendered: bool = false,
redirected: bool = false,
failed: bool = false,
redirect_state: ?RedirectState = null, redirect_state: ?RedirectState = null,
middleware_rendered: ?struct { name: []const u8, action: []const u8 } = null, middleware_rendered: ?struct { name: []const u8, action: []const u8 } = null,
middleware_rendered_during_response: bool = false, middleware_rendered_during_response: bool = false,
@ -191,9 +186,9 @@ pub fn data(self: Request, comptime root: @TypeOf(.enum_literal)) !*jetzig.Data.
/// Render a response. This function can only be called once per request (repeat calls will /// Render a response. This function can only be called once per request (repeat calls will
/// trigger an error). /// trigger an error).
pub fn render(self: *Request, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View { pub fn render(self: *Request, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View {
if (self.rendered or self.failed) self.rendered_multiple = true; if (self.state != .processed) self.rendered_multiple = true;
self.rendered = true; self.state = .rendered;
if (self.response_started) self.middleware_rendered_during_response = true; if (self.response_started) self.middleware_rendered_during_response = true;
self.rendered_view = .{ .data = self.response_data, .status_code = status_code }; self.rendered_view = .{ .data = self.response_data, .status_code = status_code };
return self.rendered_view.?; return self.rendered_view.?;
@ -202,10 +197,9 @@ pub fn render(self: *Request, status_code: jetzig.http.status_codes.StatusCode)
/// Render an error. This function can only be called once per request (repeat calls will /// Render an error. This function can only be called once per request (repeat calls will
/// trigger an error). /// trigger an error).
pub fn fail(self: *Request, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View { pub fn fail(self: *Request, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View {
if (self.rendered or self.redirected) self.rendered_multiple = true; if (self.state != .processed) self.rendered_multiple = true;
self.rendered = true; self.state = .failed;
self.failed = true;
if (self.response_started) self.middleware_rendered_during_response = true; if (self.response_started) self.middleware_rendered_during_response = true;
self.rendered_view = .{ .data = self.response_data, .status_code = status_code }; self.rendered_view = .{ .data = self.response_data, .status_code = status_code };
return self.rendered_view.?; return self.rendered_view.?;
@ -224,10 +218,9 @@ pub fn redirect(
location: []const u8, location: []const u8,
redirect_status: enum { moved_permanently, found }, redirect_status: enum { moved_permanently, found },
) jetzig.views.View { ) jetzig.views.View {
if (self.rendered or self.failed) self.rendered_multiple = true; if (self.state != .processed) self.rendered_multiple = true;
self.rendered = true; self.state = .redirected;
self.redirected = true;
if (self.response_started) self.middleware_rendered_during_response = true; if (self.response_started) self.middleware_rendered_during_response = true;
const status_code = switch (redirect_status) { const status_code = switch (redirect_status) {
@ -687,6 +680,19 @@ pub fn setTemplate(self: *Request, name: []const u8) void {
self.dynamic_assigned_template = name; self.dynamic_assigned_template = name;
} }
/// Set a custom template and render the response.
/// ```zig
/// return request.renderTemplate("blogs/comments/get", .ok);
/// ```
pub fn renderTemplate(
self: *Request,
name: []const u8,
status_code: jetzig.http.StatusCode,
) jetzig.views.View {
self.dynamic_assigned_template = name;
return self.render(status_code);
}
pub fn joinPath(self: *const Request, args: anytype) ![]const u8 { pub fn joinPath(self: *const Request, args: anytype) ![]const u8 {
const fields = std.meta.fields(@TypeOf(args)); const fields = std.meta.fields(@TypeOf(args));
var buf: [fields.len][]const u8 = undefined; var buf: [fields.len][]const u8 = undefined;

View File

@ -197,9 +197,9 @@ fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {
for (route.before_callbacks) |callback| { for (route.before_callbacks) |callback| {
try callback(request, route); try callback(request, route);
if (request.rendered_view) |view| { if (request.rendered_view) |view| {
if (request.failed) { if (request.state == .failed) {
request.setResponse(try self.renderError(request, view.status_code), .{}); request.setResponse(try self.renderError(request, view.status_code), .{});
} else if (request.rendered) { } else if (request.state == .rendered) {
// TODO: Allow callbacks to set content // TODO: Allow callbacks to set content
} }
return; return;
@ -259,7 +259,9 @@ fn renderHTML(
return request.setResponse(rendered_error, .{}); return request.setResponse(rendered_error, .{});
}; };
return if (request.redirected or request.failed or request.dynamic_assigned_template != null) return if (request.state == .redirected or
request.state == .failed or
request.dynamic_assigned_template != null)
request.setResponse(rendered, .{}) request.setResponse(rendered, .{})
else else
request.setResponse(try self.renderNotFound(request), .{}); request.setResponse(try self.renderNotFound(request), .{});
@ -327,7 +329,7 @@ fn renderView(
return try self.renderInternalServerError(request, @errorReturnTrace(), err); return try self.renderInternalServerError(request, @errorReturnTrace(), err);
}; };
if (request.failed) { if (request.state == .failed) {
const view: jetzig.views.View = request.rendered_view orelse .{ const view: jetzig.views.View = request.rendered_view orelse .{
.data = request.response_data, .data = request.response_data,
.status_code = .internal_server_error, .status_code = .internal_server_error,
@ -343,7 +345,7 @@ fn renderView(
if (request.rendered_multiple) return error.JetzigMultipleRenderError; if (request.rendered_multiple) return error.JetzigMultipleRenderError;
if (request.rendered_view) |rendered_view| { if (request.rendered_view) |rendered_view| {
if (request.redirected) return .{ .view = rendered_view, .content = "" }; if (request.state == .redirected) return .{ .view = rendered_view, .content = "" };
if (template) |capture| { if (template) |capture| {
return .{ return .{
@ -351,13 +353,17 @@ fn renderView(
.content = try self.renderTemplateWithLayout(request, capture, rendered_view, route), .content = try self.renderTemplateWithLayout(request, capture, rendered_view, route),
}; };
} else { } else {
try self.logger.DEBUG(
"Missing template for route `{s}.{s}`. Expected: `src/app/views/{s}.zmpl`.",
.{ route.view_name, @tagName(route.action), route.template },
);
return switch (request.requestFormat()) { return switch (request.requestFormat()) {
.HTML, .UNKNOWN => try self.renderNotFound(request), .HTML, .UNKNOWN => try self.renderNotFound(request),
.JSON => .{ .view = rendered_view, .content = "" }, .JSON => .{ .view = rendered_view, .content = "" },
}; };
} }
} else { } else {
if (!request.redirected) { if (request.state != .redirected) {
try self.logger.WARN("`request.render` was not invoked. Rendering empty content.", .{}); try self.logger.WARN("`request.render` was not invoked. Rendering empty content.", .{});
} }
request.response_data.reset(); request.response_data.reset();

View File

@ -72,7 +72,7 @@ pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData {
} else { } else {
try @call(.always_inline, middleware.afterRequest, .{request}); try @call(.always_inline, middleware.afterRequest, .{request});
} }
if (request.rendered or request.redirected) { if (request.state != .initial) {
request.middleware_rendered = .{ .name = @typeName(middleware), .action = "afterRequest" }; request.middleware_rendered = .{ .name = @typeName(middleware), .action = "afterRequest" };
break; break;
} }
@ -103,7 +103,10 @@ pub fn beforeResponse(
} }
} }
if (request.middleware_rendered_during_response) { if (request.middleware_rendered_during_response) {
request.middleware_rendered = .{ .name = @typeName(middleware), .action = "beforeResponse" }; request.middleware_rendered = .{
.name = @typeName(middleware),
.action = "beforeResponse",
};
break; break;
} }
} }

View File

@ -107,7 +107,7 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
if (request.middleware_rendered) |middleware| middleware.action else "", if (request.middleware_rendered) |middleware| middleware.action else "",
if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.white ++ ":" else "", if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.white ++ ":" else "",
if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.bright_cyan else "", if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.bright_cyan else "",
if (request.middleware_rendered) |_| if (request.redirected) "redirect" else "render" else "", if (request.middleware_rendered) |_| @tagName(request.state) else "",
if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.reset else "", if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.reset else "",
if (request.middleware_rendered) |_| "]" else "", if (request.middleware_rendered) |_| "]" else "",
request.path.path, request.path.path,

View File

@ -86,7 +86,7 @@ pub fn logRequest(self: ProductionLogger, request: *const jetzig.http.Request) !
if (request.middleware_rendered) |_| ":" else "", if (request.middleware_rendered) |_| ":" else "",
if (request.middleware_rendered) |middleware| middleware.action else "", if (request.middleware_rendered) |middleware| middleware.action else "",
if (request.middleware_rendered) |_| ":" else "", if (request.middleware_rendered) |_| ":" else "",
if (request.middleware_rendered) |_| if (request.redirected) "redirect" else "render" else "", if (request.middleware_rendered) |_| @tagName(request.state) else "",
if (request.middleware_rendered) |_| "]" else "", if (request.middleware_rendered) |_| "]" else "",
request.path.path, request.path.path,
}, .stdout); }, .stdout);