mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
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`.
This commit is contained in:
parent
835885a947
commit
f44a6b33c5
@ -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",
|
||||
|
@ -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} = .{{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 (std.mem.eql(u8, self.name, "new")) "/new" else "";
|
||||
return try std.mem.concat(self.routes.allocator, u8, &[_][]const u8{ "/", path, maybe_new });
|
||||
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 "";
|
||||
|
||||
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 },
|
||||
|
@ -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 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 if (isRootPath(path)) path else std.mem.trimRight(u8, path, "/");
|
||||
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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user