Handle partial union matches

This commit is contained in:
jaina heartles 2022-12-16 01:04:01 -08:00
parent cd311cfa3c
commit 91edd17801
1 changed files with 14 additions and 9 deletions

View File

@ -278,37 +278,42 @@ pub fn DeserializerContext(comptime Result: type, comptime From: type, comptime
util.deepFree(allocator, val); util.deepFree(allocator, val);
} }
const DeserializeError = error{ ParseFailure, MissingField, DuplicateUnionMember, SparseSlice, OutOfMemory };
fn deserialize( fn deserialize(
self: *@This(), self: *@This(),
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
comptime T: type, comptime T: type,
intermediary: anytype, intermediary: anytype,
comptime field_ref: FieldRef, comptime field_ref: FieldRef,
) !?T { ) DeserializeError!?T {
if (comptime Context.options.isScalar(T)) { if (comptime Context.options.isScalar(T)) {
const val = @field(intermediary.static, util.comptimeJoin(".", field_ref)); const val = @field(intermediary.static, util.comptimeJoin(".", field_ref));
return try self.context.deserializeScalar(allocator, T, val orelse return null); return self.context.deserializeScalar(allocator, T, val orelse return null) catch return error.ParseFailure;
} }
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
// At most one of any union field can be active at a time, and it is embedded // At most one of any union field can be active at a time
// in its parent container
.Union => |info| { .Union => |info| {
var result: ?T = null; var result: ?T = null;
errdefer if (result) |v| self.deserializeFree(allocator, v); errdefer if (result) |v| self.deserializeFree(allocator, v);
// TODO: errdefer cleanup var partial_match_found: bool = false;
const union_ref: FieldRef = if (Context.options.embed_unions) field_ref[0 .. field_ref.len - 1] else field_ref;
inline for (info.fields) |field| { inline for (info.fields) |field| {
const F = field.field_type; const F = field.field_type;
const new_field_ref = union_ref ++ &[_][]const u8{field.name}; const maybe_value = self.deserialize(allocator, F, intermediary, field_ref) catch |err| switch (err) {
const maybe_value = try self.deserialize(allocator, F, intermediary, new_field_ref); error.MissingField => blk: {
partial_match_found = true;
break :blk @as(?F, null);
},
else => |e| return e,
};
if (maybe_value) |value| { if (maybe_value) |value| {
// TODO: errdefer cleanup
errdefer self.deserializeFree(allocator, value); errdefer self.deserializeFree(allocator, value);
if (result != null) return error.DuplicateUnionMember; if (result != null) return error.DuplicateUnionMember;
result = @unionInit(T, field.name, value); result = @unionInit(T, field.name, value);
} }
} }
if (partial_match_found and result == null) return error.MissingField;
return result; return result;
}, },