Merge pull request #54 from jetzig-framework/empty-http-params

Closes #53: Add support for empty HTTP query param
This commit is contained in:
bobf 2024-04-06 20:49:23 +01:00 committed by GitHub
commit 8096962cf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 13 deletions

View File

@ -5,7 +5,15 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data; _ = data;
const params = try request.params(); const params = try request.params();
if (params.get("redirect")) |location| { if (params.get("redirect")) |location| {
return request.redirect(try location.toString(), .moved_permanently); switch (location.*) {
// Value is `.Null` when param is empty, e.g.:
// `http://localhost:8080/redirect?redirect`
.Null => return request.redirect("http://www.example.com/", .moved_permanently),
// Value is `.string` when param is present, e.g.:
// `http://localhost:8080/redirect?redirect=https://jetzig.dev/`
.string => |string| return request.redirect(string.value, .moved_permanently),
else => unreachable,
}
} }
return request.render(.ok); return request.render(.ok);

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("../../jetzig.zig"); const jetzig = @import("../../jetzig.zig");
const Self = @This(); const Query = @This();
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
query_string: []const u8, query_string: []const u8,
@ -10,10 +10,10 @@ data: *jetzig.data.Data,
pub const QueryItem = struct { pub const QueryItem = struct {
key: []const u8, key: []const u8,
value: []const u8, value: ?[]const u8,
}; };
pub fn init(allocator: std.mem.Allocator, query_string: []const u8, data: *jetzig.data.Data) Self { pub fn init(allocator: std.mem.Allocator, query_string: []const u8, data: *jetzig.data.Data) Query {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.query_string = query_string, .query_string = query_string,
@ -22,19 +22,19 @@ pub fn init(allocator: std.mem.Allocator, query_string: []const u8, data: *jetzi
}; };
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Query) void {
self.query_items.deinit(); self.query_items.deinit();
self.data.deinit(); self.data.deinit();
} }
pub fn parse(self: *Self) !void { pub fn parse(self: *Query) !void {
var pairs_it = std.mem.splitScalar(u8, self.query_string, '&'); var pairs_it = std.mem.splitScalar(u8, self.query_string, '&');
while (pairs_it.next()) |pair| { while (pairs_it.next()) |pair| {
var key_value_it = std.mem.splitScalar(u8, pair, '='); var key_value_it = std.mem.splitScalar(u8, pair, '=');
var count: u2 = 0; var count: u2 = 0;
var key: []const u8 = undefined; var key: []const u8 = undefined;
var value: []const u8 = undefined; var value: ?[]const u8 = null;
while (key_value_it.next()) |key_or_value| { while (key_value_it.next()) |key_or_value| {
switch (count) { switch (count) {
@ -53,27 +53,27 @@ pub fn parse(self: *Self) !void {
if (arrayParam(item.key)) |key| { if (arrayParam(item.key)) |key| {
if (params.get(key)) |value| { if (params.get(key)) |value| {
switch (value.*) { switch (value.*) {
.array => try value.array.append(self.data.string(item.value)), .array => try value.array.append(self.dataValue(item.value)),
else => return error.JetzigQueryParseError, else => return error.JetzigQueryParseError,
} }
} else { } else {
var array = try self.data.createArray(); var array = try self.data.createArray();
try array.append(self.data.string(item.value)); try array.append(self.dataValue(item.value));
try params.put(key, array); try params.put(key, array);
} }
} else if (mappingParam(item.key)) |mapping| { } else if (mappingParam(item.key)) |mapping| {
if (params.get(mapping.key)) |value| { if (params.get(mapping.key)) |value| {
switch (value.*) { switch (value.*) {
.object => try value.object.put(mapping.field, self.data.string(item.value)), .object => try value.object.put(mapping.field, self.dataValue(item.value)),
else => return error.JetzigQueryParseError, else => return error.JetzigQueryParseError,
} }
} else { } else {
var object = try self.data.createObject(); var object = try self.data.createObject();
try object.put(mapping.field, self.data.string(item.value)); try object.put(mapping.field, self.dataValue(item.value));
try params.put(mapping.key, object); try params.put(mapping.key, object);
} }
} else { } else {
try params.put(item.key, self.data.string(item.value)); try params.put(item.key, self.dataValue(item.value));
} }
} }
} }
@ -103,6 +103,14 @@ fn mappingParam(input: []const u8) ?struct { key: []const u8, field: []const u8
}; };
} }
fn dataValue(self: Query, value: ?[]const u8) *jetzig.data.Data.Value {
if (value) |item_value| {
return self.data.string(item_value);
} else {
return self.data._null();
}
}
test "simple query string" { test "simple query string" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const query_string = "foo=bar&baz=qux"; const query_string = "foo=bar&baz=qux";
@ -155,3 +163,26 @@ test "query string with mapping values" {
else => unreachable, else => unreachable,
} }
} }
test "query string with param without value" {
const allocator = std.testing.allocator;
const query_string = "foo&bar";
var data = jetzig.data.Data.init(allocator);
var query = init(allocator, query_string, &data);
defer query.deinit();
try query.parse();
const foo = try data.get("foo");
try switch (foo.*) {
.Null => {},
else => std.testing.expect(false),
};
const bar = try data.get("bar");
try switch (bar.*) {
.Null => {},
else => std.testing.expect(false),
};
}

View File

@ -146,6 +146,7 @@ fn renderHTML(
if (route) |matched_route| { if (route) |matched_route| {
const template = zmpl.find(matched_route.template); const template = zmpl.find(matched_route.template);
if (template == null) { if (template == null) {
request.response_data.noop(bool, false); // FIXME: Weird Zig bug ? Any call here fixes it.
if (try self.renderMarkdown(request, route)) |rendered_markdown| { if (try self.renderMarkdown(request, route)) |rendered_markdown| {
return request.setResponse(rendered_markdown, .{}); return request.setResponse(rendered_markdown, .{});
} }
@ -376,7 +377,7 @@ fn renderNotFound(request: *jetzig.http.Request) !RenderedView {
fn renderBadRequest(request: *jetzig.http.Request) !RenderedView { fn renderBadRequest(request: *jetzig.http.Request) !RenderedView {
request.response_data.reset(); request.response_data.reset();
const status: jetzig.http.StatusCode = .not_found; const status: jetzig.http.StatusCode = .bad_request;
const content = try request.formatStatus(status); const content = try request.formatStatus(status);
return .{ return .{
.view = jetzig.views.View{ .data = request.response_data, .status_code = status }, .view = jetzig.views.View{ .data = request.response_data, .status_code = status },