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;
const params = try request.params();
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);

View File

@ -1,7 +1,7 @@
const std = @import("std");
const jetzig = @import("../../jetzig.zig");
const Self = @This();
const Query = @This();
allocator: std.mem.Allocator,
query_string: []const u8,
@ -10,10 +10,10 @@ data: *jetzig.data.Data,
pub const QueryItem = struct {
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 .{
.allocator = allocator,
.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.data.deinit();
}
pub fn parse(self: *Self) !void {
pub fn parse(self: *Query) !void {
var pairs_it = std.mem.splitScalar(u8, self.query_string, '&');
while (pairs_it.next()) |pair| {
var key_value_it = std.mem.splitScalar(u8, pair, '=');
var count: u2 = 0;
var key: []const u8 = undefined;
var value: []const u8 = undefined;
var value: ?[]const u8 = null;
while (key_value_it.next()) |key_or_value| {
switch (count) {
@ -53,27 +53,27 @@ pub fn parse(self: *Self) !void {
if (arrayParam(item.key)) |key| {
if (params.get(key)) |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 {
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);
}
} else if (mappingParam(item.key)) |mapping| {
if (params.get(mapping.key)) |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 {
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);
}
} 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" {
const allocator = std.testing.allocator;
const query_string = "foo=bar&baz=qux";
@ -155,3 +163,26 @@ test "query string with mapping values" {
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| {
const template = zmpl.find(matched_route.template);
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| {
return request.setResponse(rendered_markdown, .{});
}
@ -376,7 +377,7 @@ fn renderNotFound(request: *jetzig.http.Request) !RenderedView {
fn renderBadRequest(request: *jetzig.http.Request) !RenderedView {
request.response_data.reset();
const status: jetzig.http.StatusCode = .not_found;
const status: jetzig.http.StatusCode = .bad_request;
const content = try request.formatStatus(status);
return .{
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },