From f44a6b33c5ac771846e93cf7e71591488b382997 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 24 Nov 2024 20:43:15 +0000 Subject: [PATCH] Routing updates Implement `/foo/1/edit` route/view. Allow setting HTTP verb by passing e.g. `/_PATCH` as the last segment of a URL - this allows browsers to submit a `POST` request with a pseudo-HTTP verb encoded in the URL which Jetzig can translate, i.e. allowing forms to submit a `PATCH`. --- build.zig.zon | 13 +-- cli/commands/generate/view.zig | 17 ++-- demo/src/app/views/root.zig | 6 ++ demo/src/app/views/session.zig | 5 + src/Routes.zig | 17 +++- src/jetzig/http/Path.zig | 162 +++++++++++++++++++++++++-------- src/jetzig/http/Request.zig | 23 ++++- src/jetzig/http/Server.zig | 4 +- src/jetzig/views/Route.zig | 4 +- 9 files changed, 186 insertions(+), 65 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 5e1ed34..4b52b49 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,16 +7,16 @@ .hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163", }, .zmpl = .{ - .url = "https://github.com/jetzig-framework/zmpl/archive/af75c8b842c3957eb97b4fc4bc49c7b2243968fa.tar.gz", - .hash = "1220ecac93d295dafd2f034a86f0979f6108d40e5ea1a39e3a2b9977c35147cac684", + .url = "https://github.com/jetzig-framework/zmpl/archive/ef1930b08e1f174ddb02a3a0a01b35aa8a4af235.tar.gz", + .hash = "1220a7bacb828f12cd013b0906da61a17fac6819ab8cee81e00d9ae1aa0faa992720", }, .jetkv = .{ .url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz", .hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af", }, .jetquery = .{ - .url = "https://github.com/jetzig-framework/jetquery/archive/a31db467c4af1c97bc7c806e1cc1a81a39162954.tar.gz", - .hash = "12203af0466ccc3a9ab57fcdf57c92c57989fa7e827d81bc98d0a5787d65402c73c3", + .url = "https://github.com/jetzig-framework/jetquery/archive/5394d7cf5d7360bd5052cd13902e26f08610423b.tar.gz", + .hash = "1220119a1ee89d8d4b7e984a82bc70fe5d57aa412b821c561ce80a93fd8806bc4b8a", }, .jetcommon = .{ .url = "https://github.com/jetzig-framework/jetcommon/archive/86f24cfdf2aaa0e8ada4539a6edef882708ced2b.tar.gz", @@ -26,10 +26,7 @@ .url = "https://github.com/ikskuh/zig-args/archive/0abdd6947a70e6d8cc83b66228cea614aa856206.tar.gz", .hash = "1220411a8c46d95bbf3b6e2059854bcb3c5159d428814099df5294232b9980517e9c", }, - .pg = .{ - .url = "https://github.com/karlseguin/pg.zig/archive/f376f4b30c63f1fdf90bc3afe246d3bc4175cd46.tar.gz", - .hash = "12200a55304988e942015b6244570b2dc0e87e5764719c9e7d5c812cd7ad34f6b138" - }, + .pg = .{ .url = "https://github.com/karlseguin/pg.zig/archive/f376f4b30c63f1fdf90bc3afe246d3bc4175cd46.tar.gz", .hash = "12200a55304988e942015b6244570b2dc0e87e5764719c9e7d5c812cd7ad34f6b138" }, .smtp_client = .{ .url = "https://github.com/karlseguin/smtp_client.zig/archive/3cbe8f269e4c3a6bce407e7ae48b2c76307c559f.tar.gz", .hash = "1220de146446d0cae4396e346cb8283dd5e086491f8577ddbd5e03ad0928111d8bc6", diff --git a/cli/commands/generate/view.zig b/cli/commands/generate/view.zig index c2c847e..7d6fb19 100644 --- a/cli/commands/generate/view.zig +++ b/cli/commands/generate/view.zig @@ -43,7 +43,7 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he const action_args = if (args.len > 1) args[1..] else - &[_][]const u8{ "index", "get", "new", "post", "put", "patch", "delete" }; + &[_][]const u8{ "index", "get", "new", "edit", "post", "put", "patch", "delete" }; var actions = std.ArrayList(Action).init(allocator); defer actions.deinit(); @@ -92,7 +92,7 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he std.debug.print("Generated view: {s}\n", .{realpath}); } -const Method = enum { index, get, new, post, put, patch, delete }; +const Method = enum { index, get, new, edit, post, put, patch, delete }; const Action = struct { method: Method, static: bool, @@ -126,15 +126,15 @@ fn writeAction(allocator: std.mem.Allocator, writer: anytype, action: Action) !v @tagName(action.method), switch (action.method) { .index, .post, .new => "", - .get, .put, .patch, .delete => "id: []const u8, ", + .get, .edit, .put, .patch, .delete => "id: []const u8, ", }, if (action.static) "StaticRequest" else "Request", switch (action.method) { .index, .post, .new => "", - .get, .put, .patch, .delete => "_ = id;\n ", + .get, .edit, .put, .patch, .delete => "_ = id;\n ", }, switch (action.method) { - .index, .get, .new => ".ok", + .index, .get, .edit, .new => ".ok", .post => ".created", .put, .patch, .delete => ".ok", }, @@ -164,17 +164,18 @@ fn writeTest(allocator: std.mem.Allocator, writer: anytype, name: []const u8, ac .{ @tagName(action.method), switch (action.method) { - .index, .get, .new => "GET", + .index, .get, .edit, .new => "GET", .put, .patch, .delete, .post => action_upper, }, name, switch (action.method) { .index, .post => "", + .edit => "/example-id/edit", .new => "/new", .get, .put, .patch, .delete => "/example-id", }, switch (action.method) { - .index, .get, .new => ".ok", + .index, .get, .new, .edit => ".ok", .post => ".created", .put, .patch, .delete => ".ok", }, @@ -208,7 +209,7 @@ fn writeStaticParams(allocator: std.mem.Allocator, actions: []Action, writer: an defer allocator.free(output); try writer.writeAll(output); }, - .get, .put, .patch, .delete => { + .get, .put, .patch, .delete, .edit => { const output = try std.fmt.allocPrint( allocator, \\ .{s} = .{{ diff --git a/demo/src/app/views/root.zig b/demo/src/app/views/root.zig index 0ed0c35..488b29b 100644 --- a/demo/src/app/views/root.zig +++ b/demo/src/app/views/root.zig @@ -16,6 +16,12 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { return request.render(.ok); } +pub fn edit(id: []const u8, request: *jetzig.Request) !jetzig.View { + var root = try request.data(.object); + try root.put("id", id); + return request.render(.ok); +} + fn customFunction(a: i32, b: i32, c: i32) i32 { return a + b + c; } diff --git a/demo/src/app/views/session.zig b/demo/src/app/views/session.zig index cec1fdb..15b0e43 100644 --- a/demo/src/app/views/session.zig +++ b/demo/src/app/views/session.zig @@ -15,6 +15,11 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { return request.render(.ok); } +pub fn edit(id: []const u8, request: *jetzig.Request) !jetzig.View { + try request.server.logger.INFO("id: {s}", .{id}); + return request.render(.ok); +} + pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { _ = data; const params = try request.params(); diff --git a/src/Routes.zig b/src/Routes.zig index 5751e8c..24dcc56 100644 --- a/src/Routes.zig +++ b/src/Routes.zig @@ -54,10 +54,20 @@ const Function = struct { defer self.routes.allocator.free(relative_path); const path = relative_path[0 .. relative_path.len - std.fs.path.extension(relative_path).len]; - if (std.mem.eql(u8, path, "root")) return try self.routes.allocator.dupe(u8, "/"); + const is_root = std.mem.eql(u8, path, "root"); + const is_new = std.mem.eql(u8, self.name, "new"); + const is_edit = std.mem.eql(u8, self.name, "edit"); + if (is_root) { + if (is_edit) return try self.routes.allocator.dupe(u8, "/edit"); + if (is_new) return try self.routes.allocator.dupe(u8, "/new"); + return try self.routes.allocator.dupe(u8, "/"); + } + + const maybe_new = if (is_new) ("/new") else ""; + // jetzig.http.Path.actionPath translates `/foo/bar/1/edit` to `/foo/bar/edit` + const maybe_edit = if (is_edit) ("/edit") else ""; - const maybe_new = if (std.mem.eql(u8, self.name, "new")) "/new" else ""; - return try std.mem.concat(self.routes.allocator, u8, &[_][]const u8{ "/", path, maybe_new }); + return try std.mem.concat(self.routes.allocator, u8, &[_][]const u8{ "/", path, maybe_new, maybe_edit }); } pub fn lessThanFn(context: void, lhs: Function, rhs: Function) bool { @@ -305,6 +315,7 @@ fn writeRoute(self: *Routes, writer: std.ArrayList(u8).Writer, route: Function) .{ "index", false }, .{ "post", false }, .{ "new", false }, + .{ "edit", true }, .{ "get", true }, .{ "edit", true }, .{ "put", true }, diff --git a/src/jetzig/http/Path.zig b/src/jetzig/http/Path.zig index 442a288..d774d2f 100644 --- a/src/jetzig/http/Path.zig +++ b/src/jetzig/http/Path.zig @@ -16,11 +16,12 @@ file_path: []const u8, resource_id: []const u8, extension: ?[]const u8, query: ?[]const u8, +method: ?jetzig.Request.Method, -const Self = @This(); +const Path = @This(); /// Initialize a new HTTP Path. -pub fn init(path: []const u8) Self { +pub fn init(path: []const u8) Path { const base_path = getBasePath(path); return .{ @@ -31,18 +32,19 @@ pub fn init(path: []const u8) Self { .resource_id = getResourceId(base_path), .extension = getExtension(path), .query = getQuery(path), + .method = getMethod(path), }; } /// No-op - no allocations currently performed. -pub fn deinit(self: *Self) void { +pub fn deinit(self: *Path) void { _ = self; } /// For a given route with a possible `:id` placeholder, return the matching URL segment for that /// placeholder. e.g. route with path `/foo/:id/bar` and request path `/foo/1234/bar` returns /// `"1234"`. -pub fn resourceId(self: Self, route: jetzig.views.Route) []const u8 { +pub fn resourceId(self: Path, route: jetzig.views.Route) []const u8 { var route_uri_path_it = std.mem.splitScalar(u8, route.uri_path, '/'); var base_path_it = std.mem.splitScalar(u8, self.base_path, '/'); @@ -54,7 +56,7 @@ pub fn resourceId(self: Self, route: jetzig.views.Route) []const u8 { return self.resource_id; } -pub fn resourceArgs(self: Self, route: jetzig.views.Route, allocator: std.mem.Allocator) ![]const []const u8 { +pub fn resourceArgs(self: Path, route: jetzig.views.Route, allocator: std.mem.Allocator) ![]const []const u8 { var args = std.ArrayList([]const u8).init(allocator); var route_uri_path_it = std.mem.splitScalar(u8, route.uri_path, '/'); var path_it = std.mem.splitScalar(u8, self.base_path, '/'); @@ -82,21 +84,41 @@ pub fn resourceArgs(self: Self, route: jetzig.views.Route, allocator: std.mem.Al // * `"/foo/bar/baz"` // * `"/foo/bar/baz.html"` // * `"/foo/bar/baz.html?qux=quux&corge=grault"` +// * `"/foo/bar/baz/_PATCH"` fn getBasePath(path: []const u8) []const u8 { - if (std.mem.indexOfScalar(u8, path, '?')) |query_index| { + const base = if (std.mem.indexOfScalar(u8, path, '?')) |query_index| blk: { if (std.mem.lastIndexOfScalar(u8, path[0..query_index], '.')) |extension_index| { - return path[0..extension_index]; + break :blk path[0..extension_index]; } else { - return path[0..query_index]; + break :blk path[0..query_index]; } - } else if (std.mem.lastIndexOfScalar(u8, path, '.')) |extension_index| { - return if (isRootPath(path[0..extension_index])) + } else if (std.mem.lastIndexOfScalar(u8, path, '.')) |extension_index| blk: { + break :blk if (isRootPath(path[0..extension_index])) path[0..extension_index] else std.mem.trimRight(u8, path[0..extension_index], "/"); - } else { - return if (isRootPath(path)) path else std.mem.trimRight(u8, path, "/"); + } else blk: { + break :blk if (isRootPath(path)) path else std.mem.trimRight(u8, path, "/"); + }; + + if (std.mem.lastIndexOfScalar(u8, base, '/')) |last_index| { + if (std.mem.startsWith(u8, base[last_index..], "/_")) { + return base[0..last_index]; + } else { + return base; + } + } else return base; +} + +fn getMethod(path: []const u8) ?jetzig.Request.Method { + var it = std.mem.splitBackwardsScalar(u8, path, '/'); + const last_segment = it.next() orelse return null; + inline for (comptime std.enums.values(jetzig.Request.Method)) |method| { + if (std.mem.startsWith(u8, last_segment, "_" ++ @tagName(method))) { + return method; + } } + return null; } // Extract `"/foo/bar"` from: @@ -131,6 +153,9 @@ fn getFilePath(path: []const u8) []const u8 { // * `"/baz"` fn getResourceId(base_path: []const u8) []const u8 { var it = std.mem.splitBackwardsScalar(u8, base_path, '/'); + + if (std.mem.endsWith(u8, base_path, "/edit")) _ = it.next(); + while (it.next()) |segment| return segment; return base_path; } @@ -164,169 +189,228 @@ fn getQuery(path: []const u8) ?[]const u8 { } } +// Extract `/foo/bar/edit` from `/foo/bar/1/edit` +// Extract `/foo/bar` from `/foo/bar/1` +pub fn actionPath(self: Path, buf: *[2048]u8) []const u8 { + if (self.path.len > 2048) return self.path; // Should never happen but we don't want to panic or overflow. + + if (std.mem.endsWith(u8, self.path, "/edit")) { + var it = std.mem.tokenizeScalar(u8, self.path, '/'); + var cursor: usize = 0; + const count = std.mem.count(u8, self.path, "/"); + var index: usize = 0; + + buf[0] = '/'; + cursor += 1; + + while (it.next()) |segment| : (index += 1) { + if (index + 2 == count) continue; // Skip ID - we special-case this in `resourceId` + @memcpy(buf[cursor .. cursor + segment.len], segment); + cursor += segment.len; + if (index + 1 < count) { + @memcpy(buf[cursor .. cursor + 1], "/"); + cursor += 1; + } + } + + return buf[0..cursor]; + } else return self.path; +} + inline fn isRootPath(path: []const u8) bool { return std.mem.eql(u8, path, "/"); } test ".base_path (with extension, with query)" { - const path = Self.init("/foo/bar/baz.html?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz.html?qux=quux&corge=grault"); try std.testing.expectEqualStrings("/foo/bar/baz", path.base_path); } test ".base_path (with extension, without query)" { - const path = Self.init("/foo/bar/baz.html"); + const path = Path.init("/foo/bar/baz.html"); try std.testing.expectEqualStrings("/foo/bar/baz", path.base_path); } test ".base_path (without extension, without query)" { - const path = Self.init("/foo/bar/baz"); + const path = Path.init("/foo/bar/baz"); try std.testing.expectEqualStrings("/foo/bar/baz", path.base_path); } test ".base_path (with trailing slash)" { - const path = Self.init("/foo/bar/"); + const path = Path.init("/foo/bar/"); try std.testing.expectEqualStrings("/foo/bar", path.base_path); } test ".base_path (root path)" { - const path = Self.init("/"); + const path = Path.init("/"); try std.testing.expectEqualStrings("/", path.base_path); } test ".base_path (root path with extension)" { - const path = Self.init("/.json"); + const path = Path.init("/.json"); try std.testing.expectEqualStrings("/", path.base_path); try std.testing.expectEqualStrings(".json", path.extension.?); } test ".directory (with extension, with query)" { - const path = Self.init("/foo/bar/baz.html?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz.html?qux=quux&corge=grault"); try std.testing.expectEqualStrings("/foo/bar", path.directory); } test ".directory (with extension, without query)" { - const path = Self.init("/foo/bar/baz.html"); + const path = Path.init("/foo/bar/baz.html"); try std.testing.expectEqualStrings("/foo/bar", path.directory); } test ".directory (without extension, without query)" { - const path = Self.init("/foo/bar/baz"); + const path = Path.init("/foo/bar/baz"); try std.testing.expectEqualStrings("/foo/bar", path.directory); } test ".directory (without extension, without query, root path)" { - const path = Self.init("/"); + const path = Path.init("/"); try std.testing.expectEqualStrings("/", path.directory); } test ".resource_id (with extension, with query)" { - const path = Self.init("/foo/bar/baz.html?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz.html?qux=quux&corge=grault"); try std.testing.expectEqualStrings("baz", path.resource_id); } test ".resource_id (with extension, without query)" { - const path = Self.init("/foo/bar/baz.html"); + const path = Path.init("/foo/bar/baz.html"); try std.testing.expectEqualStrings("baz", path.resource_id); } test ".resource_id (without extension, without query)" { - const path = Self.init("/foo/bar/baz"); + const path = Path.init("/foo/bar/baz"); try std.testing.expectEqualStrings("baz", path.resource_id); } test ".resource_id (without extension, without query, without base path)" { - const path = Self.init("/baz"); + const path = Path.init("/baz"); try std.testing.expectEqualStrings("baz", path.resource_id); } test ".resource_id (with trailing slash)" { - const path = Self.init("/foo/bar/"); + const path = Path.init("/foo/bar/"); try std.testing.expectEqualStrings("bar", path.resource_id); } test ".extension (with query)" { - const path = Self.init("/foo/bar/baz.html?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz.html?qux=quux&corge=grault"); try std.testing.expectEqualStrings(".html", path.extension.?); } test ".extension (without query)" { - const path = Self.init("/foo/bar/baz.html"); + const path = Path.init("/foo/bar/baz.html"); try std.testing.expectEqualStrings(".html", path.extension.?); } test ".extension (without extension)" { - const path = Self.init("/foo/bar/baz"); + const path = Path.init("/foo/bar/baz"); try std.testing.expect(path.extension == null); } test ".query (with extension, with query)" { - const path = Self.init("/foo/bar/baz.html?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz.html?qux=quux&corge=grault"); try std.testing.expectEqualStrings(path.query.?, "qux=quux&corge=grault"); } test ".query (without extension, with query)" { - const path = Self.init("/foo/bar/baz?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz?qux=quux&corge=grault"); try std.testing.expectEqualStrings(path.query.?, "qux=quux&corge=grault"); } test ".query (with extension, without query)" { - const path = Self.init("/foo/bar/baz.json"); + const path = Path.init("/foo/bar/baz.json"); try std.testing.expect(path.query == null); } test ".query (without extension, without query)" { - const path = Self.init("/foo/bar/baz"); + const path = Path.init("/foo/bar/baz"); try std.testing.expect(path.query == null); } test ".query (with empty query)" { - const path = Self.init("/foo/bar/baz?"); + const path = Path.init("/foo/bar/baz?"); try std.testing.expect(path.query == null); } test ".file_path (with extension, with query)" { - const path = Self.init("/foo/bar/baz.json?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz.json?qux=quux&corge=grault"); try std.testing.expectEqualStrings("/foo/bar/baz.json", path.file_path); } test ".file_path (with extension, without query)" { - const path = Self.init("/foo/bar/baz.json"); + const path = Path.init("/foo/bar/baz.json"); try std.testing.expectEqualStrings("/foo/bar/baz.json", path.file_path); } test ".file_path (without extension, without query)" { - const path = Self.init("/foo/bar/baz"); + const path = Path.init("/foo/bar/baz"); try std.testing.expectEqualStrings("/foo/bar/baz", path.file_path); } test ".file_path (without extension, with query)" { - const path = Self.init("/foo/bar/baz?qux=quux&corge=grault"); + const path = Path.init("/foo/bar/baz?qux=quux&corge=grault"); try std.testing.expectEqualStrings("/foo/bar/baz", path.file_path); } + +test ".resource_id (/foo/bar/123/edit)" { + const path = Path.init("/foo/bar/123/edit"); + + try std.testing.expectEqualStrings("123", path.resource_id); +} + +test ".actionPath (/foo/bar/123/edit)" { + var buf: [2048]u8 = undefined; + const path = Path.init("/foo/bar/123/edit").actionPath(&buf); + + try std.testing.expectEqualStrings("/foo/bar/edit", path); +} + +test ".actionPath (/foo/bar)" { + var buf: [2048]u8 = undefined; + const path = Path.init("/foo/bar").actionPath(&buf); + + try std.testing.expectEqualStrings("/foo/bar", path); +} + +test ".base_path (/foo/bar/1/_PATCH" { + const path = Path.init("/foo/bar/1/_PATCH"); + try std.testing.expectEqualStrings("/foo/bar/1", path.base_path); + try std.testing.expectEqualStrings("1", path.resource_id); +} + +test ".method (/foo/bar/1/_PATCH" { + const path = Path.init("/foo/bar/1/_PATCH"); + try std.testing.expect(path.method.? == .PATCH); +} diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index 41de5c9..66902df 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -119,7 +119,11 @@ pub fn init( response: *jetzig.http.Response, repo: *jetzig.database.Repo, ) !Request { - const method = switch (httpz_request.method) { + const path = jetzig.http.Path.init(httpz_request.url.raw); + + // We can fake the HTTP method by appending `/_PATCH` (e.g.) to the end of the URL. + // This allows using PATCH, PUT, DELETE from HTML forms. + const method = path.method orelse switch (httpz_request.method) { .DELETE => Method.DELETE, .GET => Method.GET, .PATCH => Method.PATCH, @@ -134,7 +138,7 @@ pub fn init( return .{ .allocator = allocator, - .path = jetzig.http.Path.init(httpz_request.url.raw), + .path = path, .method = method, .headers = jetzig.http.Headers.init(allocator, httpz_request.headers), .server = server, @@ -764,6 +768,7 @@ pub fn match(self: *Request, route: jetzig.views.Route) !bool { .index => self.isMatch(.exact, route), .get => self.isMatch(.resource_id, route), .new => self.isMatch(.exact, route), + .edit => self.isMatch(.exact, route), else => false, }, .POST => switch (route.action) { @@ -796,8 +801,18 @@ fn isMatch( .resource_id => self.path.directory, }; - // Special case for `/foobar/1/new` -> render `new()` - if (route.action == .get and std.mem.eql(u8, self.path.resource_id, "new")) return false; + if (route.action == .get) { + // Special case for `/foobar/1/new` -> render `new()` - prevent matching `get` + if (std.mem.eql(u8, self.path.resource_id, "new")) return false; + // Special case for `/foobar/1/edit` -> render `edit()` - prevent matching `get` + if (std.mem.eql(u8, self.path.resource_id, "edit")) return false; + } + + if (route.action == .edit and std.mem.endsWith(u8, self.path.path, "/edit")) { + var buf: [2048]u8 = undefined; + const action_path = self.path.actionPath(&buf); + if (std.mem.eql(u8, action_path, route.uri_path)) return true; + } return std.mem.eql(u8, path, route.uri_path); } diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index ca0c5d4..cb5ce7a 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -786,7 +786,7 @@ fn matchStaticContent(self: *Server, request: *jetzig.http.Request) !?[]const u8 self.decoded_static_route_params[index].get("params"), route, request, - params, + params.*, )) return switch (request_format) { .HTML, .UNKNOWN => static_output.output.html, .JSON => static_output.output.json, @@ -822,7 +822,7 @@ fn matchStaticOutput( maybe_expected_params: ?*jetzig.data.Value, route: jetzig.views.Route, request: *const jetzig.http.Request, - params: *jetzig.data.Value, + params: jetzig.data.Value, ) bool { return if (maybe_expected_params) |expected_params| blk: { const params_match = expected_params.count() == 0 or expected_params.eql(params); diff --git a/src/jetzig/views/Route.zig b/src/jetzig/views/Route.zig index 851dacf..c60b79d 100644 --- a/src/jetzig/views/Route.zig +++ b/src/jetzig/views/Route.zig @@ -5,7 +5,7 @@ const view_types = @import("view_types.zig"); const Route = @This(); -pub const Action = enum { index, get, new, post, put, patch, delete, custom }; +pub const Action = enum { index, get, new, edit, post, put, patch, delete, custom }; pub const View = union(enum) { with_id: view_types.ViewWithId, @@ -32,6 +32,7 @@ pub const Formats = struct { index: ?[]const ResponseFormat = null, get: ?[]const ResponseFormat = null, new: ?[]const ResponseFormat = null, + edit: ?[]const ResponseFormat = null, post: ?[]const ResponseFormat = null, put: ?[]const ResponseFormat = null, patch: ?[]const ResponseFormat = null, @@ -114,6 +115,7 @@ pub fn validateFormat(self: Route, request: *const jetzig.http.Request) bool { .index => formats.index orelse return true, .get => formats.get orelse return true, .new => formats.new orelse return true, + .edit => formats.edit orelse return true, .post => formats.post orelse return true, .put => formats.put orelse return true, .patch => formats.patch orelse return true,