Decode query string chars
This commit is contained in:
parent
db1bd0f7c7
commit
a15baaf2e7
2 changed files with 91 additions and 26 deletions
|
@ -70,7 +70,7 @@ const QueryIter = @import("util").QueryIter;
|
|||
///
|
||||
/// TODO: values are currently case-sensitive, and are not url-decoded properly.
|
||||
/// This should be fixed.
|
||||
pub fn parseQuery(comptime T: type, query: []const u8) !T {
|
||||
pub fn parseQuery(alloc: std.mem.Allocator, comptime T: type, query: []const u8) !T {
|
||||
if (comptime !std.meta.trait.isContainer(T)) @compileError("T must be a struct");
|
||||
var iter = QueryIter.from(query);
|
||||
|
||||
|
@ -85,27 +85,54 @@ pub fn parseQuery(comptime T: type, query: []const u8) !T {
|
|||
} else std.log.debug("unknown param {s}", .{pair.key});
|
||||
}
|
||||
|
||||
return (try parse(T, "", "", fields)).?;
|
||||
return (try parse(alloc, T, "", "", fields)).?;
|
||||
}
|
||||
|
||||
fn parseScalar(comptime T: type, comptime name: []const u8, fields: anytype) !?T {
|
||||
fn decodeString(alloc: std.mem.Allocator, val: []const u8) ![]const u8 {
|
||||
var list = try std.ArrayList(u8).initCapacity(alloc, val.len);
|
||||
errdefer list.deinit();
|
||||
|
||||
var idx: usize = 0;
|
||||
while (idx < val.len) : (idx += 1) {
|
||||
if (val[idx] != '%') {
|
||||
try list.append(val[idx]);
|
||||
} else {
|
||||
if (val.len < idx + 2) return error.InvalidEscape;
|
||||
const buf = [2]u8{ val[idx + 1], val[idx + 2] };
|
||||
idx += 2;
|
||||
|
||||
const ch = try std.fmt.parseInt(u8, &buf, 16);
|
||||
try list.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn parseScalar(alloc: std.mem.Allocator, comptime T: type, comptime name: []const u8, fields: anytype) !?T {
|
||||
const param = @field(fields, name);
|
||||
return switch (param) {
|
||||
.not_specified => null,
|
||||
.no_value => try parseQueryValue(T, null),
|
||||
.value => |v| try parseQueryValue(T, v),
|
||||
.no_value => try parseQueryValue(alloc, T, null),
|
||||
.value => |v| try parseQueryValue(alloc, T, v),
|
||||
};
|
||||
}
|
||||
|
||||
fn parse(comptime T: type, comptime prefix: []const u8, comptime name: []const u8, fields: anytype) !?T {
|
||||
if (comptime isScalar(T)) return parseScalar(T, prefix ++ "." ++ name, fields);
|
||||
fn parse(
|
||||
alloc: std.mem.Allocator,
|
||||
comptime T: type,
|
||||
comptime prefix: []const u8,
|
||||
comptime name: []const u8,
|
||||
fields: anytype,
|
||||
) !?T {
|
||||
if (comptime isScalar(T)) return parseScalar(alloc, T, prefix ++ "." ++ name, fields);
|
||||
switch (@typeInfo(T)) {
|
||||
.Union => |info| {
|
||||
var result: ?T = null;
|
||||
inline for (info.fields) |field| {
|
||||
const F = field.field_type;
|
||||
|
||||
const maybe_value = try parse(F, prefix, field.name, fields);
|
||||
const maybe_value = try parse(alloc, F, prefix, field.name, fields);
|
||||
if (maybe_value) |value| {
|
||||
if (result != null) return error.DuplicateUnionField;
|
||||
|
||||
|
@ -124,7 +151,7 @@ fn parse(comptime T: type, comptime prefix: []const u8, comptime name: []const u
|
|||
const F = field.field_type;
|
||||
|
||||
var maybe_value: ?F = null;
|
||||
if (try parse(F, prefix ++ "." ++ name, field.name, fields)) |v| {
|
||||
if (try parse(alloc, F, prefix ++ "." ++ name, field.name, fields)) |v| {
|
||||
maybe_value = v;
|
||||
} else if (field.default_value) |default| {
|
||||
if (comptime @sizeOf(F) != 0) {
|
||||
|
@ -151,7 +178,7 @@ fn parse(comptime T: type, comptime prefix: []const u8, comptime name: []const u
|
|||
},
|
||||
|
||||
// Only applies to non-scalar optionals
|
||||
.Optional => |info| return try parse(info.child, prefix, name, fields),
|
||||
.Optional => |info| return try parse(alloc, info.child, prefix, name, fields),
|
||||
|
||||
else => @compileError("tmp"),
|
||||
}
|
||||
|
@ -204,7 +231,7 @@ fn Intermediary(comptime T: type) type {
|
|||
} });
|
||||
}
|
||||
|
||||
fn parseQueryValue(comptime T: type, value: ?[]const u8) !T {
|
||||
fn parseQueryValue(alloc: std.mem.Allocator, comptime T: type, value: ?[]const u8) !T {
|
||||
const is_optional = comptime std.meta.trait.is(.Optional)(T);
|
||||
// If param is present, but without an associated value
|
||||
if (value == null) {
|
||||
|
@ -216,7 +243,7 @@ fn parseQueryValue(comptime T: type, value: ?[]const u8) !T {
|
|||
error.InvalidValue;
|
||||
}
|
||||
|
||||
return try parseQueryValueNotNull(if (is_optional) std.meta.Child(T) else T, value.?);
|
||||
return try parseQueryValueNotNull(alloc, if (is_optional) std.meta.Child(T) else T, value.?);
|
||||
}
|
||||
|
||||
const bool_map = std.ComptimeStringMap(bool, .{
|
||||
|
@ -233,15 +260,27 @@ const bool_map = std.ComptimeStringMap(bool, .{
|
|||
.{ "0", false },
|
||||
});
|
||||
|
||||
fn parseQueryValueNotNull(comptime T: type, value: []const u8) !T {
|
||||
if (comptime std.meta.trait.isZigString(T)) return value;
|
||||
if (comptime std.meta.trait.isIntegral(T)) return try std.fmt.parseInt(T, value, 0);
|
||||
if (comptime std.meta.trait.isFloat(T)) return try std.fmt.parseFloat(T, value);
|
||||
if (comptime std.meta.trait.is(.Enum)(T)) return std.meta.stringToEnum(T, value) orelse error.InvalidEnumValue;
|
||||
if (T == bool) return bool_map.get(value) orelse error.InvalidBool;
|
||||
if (comptime std.meta.trait.hasFn("parse")(T)) return try T.parse(value);
|
||||
fn parseQueryValueNotNull(alloc: std.mem.Allocator, comptime T: type, value: []const u8) !T {
|
||||
const decoded = try decodeString(alloc, value);
|
||||
errdefer alloc.free(decoded);
|
||||
|
||||
if (comptime std.meta.trait.isZigString(T)) return decoded;
|
||||
|
||||
const result = if (comptime std.meta.trait.isIntegral(T))
|
||||
try std.fmt.parseInt(T, decoded, 0)
|
||||
else if (comptime std.meta.trait.isFloat(T))
|
||||
try std.fmt.parseFloat(T, decoded)
|
||||
else if (comptime std.meta.trait.is(.Enum)(T))
|
||||
std.meta.stringToEnum(T, decoded) orelse return error.InvalidEnumValue
|
||||
else if (T == bool)
|
||||
bool_map.get(value) orelse return error.InvalidBool
|
||||
else if (comptime std.meta.trait.hasFn("parse")(T))
|
||||
try T.parse(value)
|
||||
else
|
||||
@compileError("Invalid type " ++ @typeName(T));
|
||||
|
||||
alloc.free(decoded);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn isScalar(comptime T: type) bool {
|
||||
|
@ -261,14 +300,34 @@ pub fn formatQuery(params: anytype, writer: anytype) !void {
|
|||
try format("", "", params, writer);
|
||||
}
|
||||
|
||||
fn urlFormatString(writer: anytype, val: []const u8) !void {
|
||||
for (val) |ch| {
|
||||
const printable = switch (ch) {
|
||||
'0'...'9', 'a'...'z', 'A'...'Z' => true,
|
||||
'-', '.', '_', '~', ':', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
|
||||
else => false,
|
||||
};
|
||||
|
||||
try if (printable) writer.writeByte(ch) else std.fmt.format(writer, "%{x:0>2}", .{ch});
|
||||
}
|
||||
}
|
||||
|
||||
fn formatScalar(comptime name: []const u8, val: anytype, writer: anytype) !void {
|
||||
const T = @TypeOf(val);
|
||||
if (comptime std.meta.trait.isZigString(T)) return std.fmt.format(writer, "{s}={s}&", .{ name, val });
|
||||
_ = try switch (@typeInfo(T)) {
|
||||
.Enum => std.fmt.format(writer, "{s}={s}&", .{ name, @tagName(val) }),
|
||||
.Optional => if (val) |v| formatScalar(name, v, writer),
|
||||
else => std.fmt.format(writer, "{s}={}&", .{ name, val }),
|
||||
if (comptime std.meta.trait.is(.Optional)(T)) {
|
||||
return if (val) |v| formatScalar(name, v, writer) else {};
|
||||
}
|
||||
|
||||
try urlFormatString(writer, name);
|
||||
try writer.writeByte('=');
|
||||
if (comptime std.meta.trait.isZigString(T)) {
|
||||
try urlFormatString(writer, val);
|
||||
} else try switch (@typeInfo(T)) {
|
||||
.Enum => urlFormatString(writer, @tagName(val)),
|
||||
else => std.fmt.format(writer, "{}", .{val}),
|
||||
};
|
||||
|
||||
try writer.writeByte('&');
|
||||
}
|
||||
|
||||
fn format(comptime prefix: []const u8, comptime name: []const u8, params: anytype, writer: anytype) !void {
|
||||
|
|
|
@ -99,7 +99,13 @@ pub fn deepFree(alloc: ?std.mem.Allocator, val: anytype) void {
|
|||
},
|
||||
.Optional => if (val) |v| deepFree(alloc, v) else {},
|
||||
.Struct => |struct_info| inline for (struct_info.fields) |field| deepFree(alloc, @field(val, field.name)),
|
||||
.Union, .ErrorUnion => @compileError("TODO: Unions not yet supported by deepFree"),
|
||||
.Union => |union_info| inline for (union_info.fields) |field| {
|
||||
const tag = @field(std.meta.Tag(T), field.name);
|
||||
if (@as(std.meta.Tag(T), val) == tag) {
|
||||
deepFree(alloc, @field(val, field.name));
|
||||
}
|
||||
},
|
||||
.ErrorUnion => if (val) |v| deepFree(alloc, v) else {},
|
||||
.Array => for (val) |v| deepFree(alloc, v),
|
||||
|
||||
.Enum, .Int, .Float, .Bool, .Void, .Type => {},
|
||||
|
|
Loading…
Reference in a new issue