Multipart deserialization
This commit is contained in:
parent
2f78490545
commit
96a46a98c9
1 changed files with 143 additions and 14 deletions
|
@ -4,8 +4,7 @@ const util = @import("util");
|
||||||
const max_boundary = 70;
|
const max_boundary = 70;
|
||||||
|
|
||||||
const FormFieldResult = struct {
|
const FormFieldResult = struct {
|
||||||
value: []const u8,
|
field: FormField,
|
||||||
params: FormDataParams,
|
|
||||||
more: bool,
|
more: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -138,13 +137,15 @@ fn parseFormField(boundary: []const u8, peek_stream: anytype, alloc: std.mem.All
|
||||||
const disposition = headers.get("Content-Disposition") orelse return error.NoDisposition;
|
const disposition = headers.get("Content-Disposition") orelse return error.NoDisposition;
|
||||||
|
|
||||||
return FormFieldResult{
|
return FormFieldResult{
|
||||||
.value = value.toOwnedSlice(),
|
.field = .{
|
||||||
.params = try parseParams(alloc, FormDataParams, disposition),
|
.value = value.toOwnedSlice(),
|
||||||
|
.params = try parseParams(alloc, FormDataParams, disposition),
|
||||||
|
},
|
||||||
.more = !terminal,
|
.more = !terminal,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parseFormData(boundary: []const u8, reader: anytype, alloc: std.mem.Allocator) !void {
|
pub fn parseFormData(comptime T: type, boundary: []const u8, reader: anytype, alloc: std.mem.Allocator) !T {
|
||||||
if (boundary.len > max_boundary) return error.BoundaryTooLarge;
|
if (boundary.len > max_boundary) return error.BoundaryTooLarge;
|
||||||
|
|
||||||
var stream = std.io.peekStream(72, reader);
|
var stream = std.io.peekStream(72, reader);
|
||||||
|
@ -156,17 +157,137 @@ pub fn parseFormData(boundary: []const u8, reader: anytype, alloc: std.mem.Alloc
|
||||||
if (!std.mem.startsWith(u8, line, "--")) return error.InvalidMultipartBoundary;
|
if (!std.mem.startsWith(u8, line, "--")) return error.InvalidMultipartBoundary;
|
||||||
if (!std.mem.endsWith(u8, line, boundary)) return error.InvalidMultipartBoundary;
|
if (!std.mem.endsWith(u8, line, boundary)) return error.InvalidMultipartBoundary;
|
||||||
|
|
||||||
if (try isFinalPart(&stream)) return;
|
if (try isFinalPart(&stream)) return error.NoForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fields = Intermediary(T){};
|
||||||
while (true) {
|
while (true) {
|
||||||
const field = try parseFormField(boundary, &stream, alloc);
|
const form_field = try parseFormField(boundary, &stream, alloc);
|
||||||
defer util.deepFree(alloc, field);
|
|
||||||
|
|
||||||
std.debug.print("{any}\n", .{field});
|
inline for (std.meta.fields(Intermediary(T))) |field| {
|
||||||
|
if (std.ascii.eqlIgnoreCase(field.name[2..], form_field.field.params.name.?)) {
|
||||||
|
@field(fields, field.name) = form_field.field;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std.log.debug("unknown form field {?s}", .{form_field.field.params.name});
|
||||||
|
util.deepFree(alloc, form_field);
|
||||||
|
}
|
||||||
|
|
||||||
if (!field.more) return;
|
if (!form_field.more) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (try parse(alloc, T, "", "", 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 try parseFormValue(alloc, T, @field(fields, prefix ++ "." ++ name));
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Union => |info| {
|
||||||
|
var result: ?T = null;
|
||||||
|
inline for (info.fields) |field| {
|
||||||
|
const F = field.field_type;
|
||||||
|
|
||||||
|
const maybe_value = try parse(alloc, F, prefix, field.name, fields);
|
||||||
|
if (maybe_value) |value| {
|
||||||
|
if (result != null) return error.DuplicateUnionField;
|
||||||
|
|
||||||
|
result = @unionInit(T, field.name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std.log.debug("{any}", .{result});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Struct => |info| {
|
||||||
|
var result: T = undefined;
|
||||||
|
var fields_specified: usize = 0;
|
||||||
|
errdefer inline for (info.fields) |field, i| {
|
||||||
|
if (fields_specified < i) util.deepFree(alloc, @field(result, field.name));
|
||||||
|
};
|
||||||
|
|
||||||
|
inline for (info.fields) |field| {
|
||||||
|
const F = field.field_type;
|
||||||
|
|
||||||
|
var maybe_value: ?F = null;
|
||||||
|
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) {
|
||||||
|
maybe_value = try util.deepClone(alloc, @ptrCast(*const F, @alignCast(@alignOf(F), default)).*);
|
||||||
|
} else {
|
||||||
|
maybe_value = std.mem.zeroes(F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maybe_value) |v| {
|
||||||
|
fields_specified += 1;
|
||||||
|
@field(result, field.name) = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields_specified == 0) {
|
||||||
|
return null;
|
||||||
|
} else if (fields_specified != info.fields.len) {
|
||||||
|
std.log.debug("{} {s} {s}", .{ T, prefix, name });
|
||||||
|
return error.PartiallySpecifiedStruct;
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Only applies to non-scalar optionals
|
||||||
|
.Optional => |info| return try parse(alloc, info.child, prefix, name, fields),
|
||||||
|
|
||||||
|
else => @compileError("tmp"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recursiveFieldPaths(comptime T: type, comptime prefix: []const u8) []const []const u8 {
|
||||||
|
comptime {
|
||||||
|
if (std.meta.trait.is(.Optional)(T)) return recursiveFieldPaths(std.meta.Child(T), prefix);
|
||||||
|
|
||||||
|
var fields: []const []const u8 = &.{};
|
||||||
|
|
||||||
|
for (std.meta.fields(T)) |f| {
|
||||||
|
const full_name = prefix ++ f.name;
|
||||||
|
|
||||||
|
if (isScalar(f.field_type)) {
|
||||||
|
fields = fields ++ @as([]const []const u8, &.{full_name});
|
||||||
|
} else {
|
||||||
|
const field_prefix = if (@typeInfo(f.field_type) == .Union) prefix else full_name ++ ".";
|
||||||
|
fields = fields ++ recursiveFieldPaths(f.field_type, field_prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Intermediary(comptime T: type) type {
|
||||||
|
const field_names = recursiveFieldPaths(T, "..");
|
||||||
|
|
||||||
|
var fields: [field_names.len]std.builtin.Type.StructField = undefined;
|
||||||
|
for (field_names) |name, i| fields[i] = .{
|
||||||
|
.name = name,
|
||||||
|
.field_type = ?FormField,
|
||||||
|
.default_value = &@as(?FormField, null),
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = @alignOf(?FormField),
|
||||||
|
};
|
||||||
|
|
||||||
|
return @Type(.{ .Struct = .{
|
||||||
|
.layout = .Auto,
|
||||||
|
.fields = &fields,
|
||||||
|
.decls = &.{},
|
||||||
|
.is_tuple = false,
|
||||||
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormFile = struct {
|
const FormFile = struct {
|
||||||
|
@ -174,9 +295,8 @@ const FormFile = struct {
|
||||||
data: []const u8,
|
data: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parseFormValue(alloc: std.mem.Allocator, comptime T: type, field: FormField) !T {
|
fn parseFormValue(alloc: std.mem.Allocator, comptime T: type, f: ?FormField) !T {
|
||||||
const is_optional = std.meta.trait.is(.Optional)(T);
|
const field = f orelse unreachable;
|
||||||
if ((comptime is_optional) and field.value.len == 0) return null;
|
|
||||||
|
|
||||||
if (comptime std.meta.trait.isZigString(T)) return field.value;
|
if (comptime std.meta.trait.isZigString(T)) return field.value;
|
||||||
|
|
||||||
|
@ -275,6 +395,15 @@ test "parseFormData" {
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const T = struct {
|
||||||
|
first: []const u8,
|
||||||
|
second: []const u8,
|
||||||
|
third: []const u8,
|
||||||
|
};
|
||||||
var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
||||||
try parseFormData("abcd", stream.reader(), std.testing.allocator);
|
const result = try parseFormData(T, "abcd", stream.reader(), std.testing.allocator);
|
||||||
|
std.debug.print("\nfirst: {s}\n\n", .{result.first});
|
||||||
|
std.debug.print("\nsecond: {s}\n\n", .{result.second});
|
||||||
|
std.debug.print("\nthird: {s}\n\n", .{result.third});
|
||||||
|
std.debug.print("\n{any}\n\n", .{result});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue