diff --git a/demo/src/app/views/render_template.zig b/demo/src/app/views/render_template.zig
new file mode 100644
index 0000000..bb352bf
--- /dev/null
+++ b/demo/src/app/views/render_template.zig
@@ -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");
+}
diff --git a/demo/src/app/views/render_template/index.zmpl b/demo/src/app/views/render_template/index.zmpl
new file mode 100644
index 0000000..76457d0
--- /dev/null
+++ b/demo/src/app/views/render_template/index.zmpl
@@ -0,0 +1,3 @@
+
+ Content goes here
+
diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig
index b864913..b8e74de 100644
--- a/src/jetzig/http/Request.zig
+++ b/src/jetzig/http/Request.zig
@@ -30,16 +30,11 @@ parsed_multipart: ?*jetzig.data.Data = null,
_cookies: ?*jetzig.http.Cookies = null,
_session: ?*jetzig.http.Session = null,
body: []const u8 = undefined,
-state: enum { initial, processed } = .initial,
+state: enum { initial, rendered, redirected, failed, processed } = .initial,
response_started: bool = false,
dynamic_assigned_template: ?[]const u8 = null,
layout: ?[]const u8 = null,
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,
middleware_rendered: ?struct { name: []const u8, action: []const u8 } = null,
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
/// trigger an error).
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;
self.rendered_view = .{ .data = self.response_data, .status_code = status_code };
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
/// trigger an error).
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.failed = true;
+ self.state = .failed;
if (self.response_started) self.middleware_rendered_during_response = true;
self.rendered_view = .{ .data = self.response_data, .status_code = status_code };
return self.rendered_view.?;
@@ -224,10 +218,9 @@ pub fn redirect(
location: []const u8,
redirect_status: enum { moved_permanently, found },
) 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.redirected = true;
+ self.state = .redirected;
if (self.response_started) self.middleware_rendered_during_response = true;
const status_code = switch (redirect_status) {
@@ -687,6 +680,19 @@ pub fn setTemplate(self: *Request, name: []const u8) void {
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 {
const fields = std.meta.fields(@TypeOf(args));
var buf: [fields.len][]const u8 = undefined;
diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig
index 819067a..28568fa 100644
--- a/src/jetzig/http/Server.zig
+++ b/src/jetzig/http/Server.zig
@@ -197,9 +197,9 @@ fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {
for (route.before_callbacks) |callback| {
try callback(request, route);
if (request.rendered_view) |view| {
- if (request.failed) {
+ if (request.state == .failed) {
request.setResponse(try self.renderError(request, view.status_code), .{});
- } else if (request.rendered) {
+ } else if (request.state == .rendered) {
// TODO: Allow callbacks to set content
}
return;
@@ -259,7 +259,9 @@ fn renderHTML(
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, .{})
else
request.setResponse(try self.renderNotFound(request), .{});
@@ -327,7 +329,7 @@ fn renderView(
return try self.renderInternalServerError(request, @errorReturnTrace(), err);
};
- if (request.failed) {
+ if (request.state == .failed) {
const view: jetzig.views.View = request.rendered_view orelse .{
.data = request.response_data,
.status_code = .internal_server_error,
@@ -343,7 +345,7 @@ fn renderView(
if (request.rendered_multiple) return error.JetzigMultipleRenderError;
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| {
return .{
@@ -351,13 +353,17 @@ fn renderView(
.content = try self.renderTemplateWithLayout(request, capture, rendered_view, route),
};
} 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()) {
.HTML, .UNKNOWN => try self.renderNotFound(request),
.JSON => .{ .view = rendered_view, .content = "" },
};
}
} else {
- if (!request.redirected) {
+ if (request.state != .redirected) {
try self.logger.WARN("`request.render` was not invoked. Rendering empty content.", .{});
}
request.response_data.reset();
diff --git a/src/jetzig/http/middleware.zig b/src/jetzig/http/middleware.zig
index a6feace..31dc50c 100644
--- a/src/jetzig/http/middleware.zig
+++ b/src/jetzig/http/middleware.zig
@@ -72,7 +72,7 @@ pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData {
} else {
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" };
break;
}
@@ -103,7 +103,10 @@ pub fn beforeResponse(
}
}
if (request.middleware_rendered_during_response) {
- request.middleware_rendered = .{ .name = @typeName(middleware), .action = "beforeResponse" };
+ request.middleware_rendered = .{
+ .name = @typeName(middleware),
+ .action = "beforeResponse",
+ };
break;
}
}
diff --git a/src/jetzig/loggers/DevelopmentLogger.zig b/src/jetzig/loggers/DevelopmentLogger.zig
index 3ae6ba9..8ff0dbf 100644
--- a/src/jetzig/loggers/DevelopmentLogger.zig
+++ b/src/jetzig/loggers/DevelopmentLogger.zig
@@ -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) |_| 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) |_| 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) |_| "]" else "",
request.path.path,
diff --git a/src/jetzig/loggers/ProductionLogger.zig b/src/jetzig/loggers/ProductionLogger.zig
index 6c7c51c..65c7ffb 100644
--- a/src/jetzig/loggers/ProductionLogger.zig
+++ b/src/jetzig/loggers/ProductionLogger.zig
@@ -86,7 +86,7 @@ pub fn logRequest(self: ProductionLogger, request: *const jetzig.http.Request) !
if (request.middleware_rendered) |_| ":" else "",
if (request.middleware_rendered) |middleware| middleware.action 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 "",
request.path.path,
}, .stdout);