From 8095bbcb76018c9f442e655a634f2f7f696b5a94 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sat, 23 Nov 2024 13:42:14 +0000 Subject: [PATCH] Implement `request.renderTemplate` Clean up request state. --- demo/src/app/views/render_template.zig | 15 ++++++++ demo/src/app/views/render_template/index.zmpl | 3 ++ src/jetzig/http/Request.zig | 34 +++++++++++-------- src/jetzig/http/Server.zig | 18 ++++++---- src/jetzig/http/middleware.zig | 7 ++-- src/jetzig/loggers/DevelopmentLogger.zig | 2 +- src/jetzig/loggers/ProductionLogger.zig | 2 +- 7 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 demo/src/app/views/render_template.zig create mode 100644 demo/src/app/views/render_template/index.zmpl 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);