2022-12-01 03:21:55 +00:00
|
|
|
const std = @import("std");
|
|
|
|
const util = @import("./lib.zig");
|
|
|
|
|
2022-12-03 06:34:12 +00:00
|
|
|
pub const FieldRef = []const []const u8;
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-01 09:56:17 +00:00
|
|
|
pub fn defaultIsScalar(comptime T: type) bool {
|
2022-12-01 05:11:54 +00:00
|
|
|
if (comptime std.meta.trait.is(.Optional)(T) and defaultIsScalar(std.meta.Child(T))) return true;
|
|
|
|
if (comptime std.meta.trait.isZigString(T)) return true;
|
|
|
|
if (comptime std.meta.trait.isIntegral(T)) return true;
|
|
|
|
if (comptime std.meta.trait.isFloat(T)) return true;
|
|
|
|
if (comptime std.meta.trait.is(.Enum)(T)) return true;
|
|
|
|
if (comptime std.meta.trait.is(.EnumLiteral)(T)) return true;
|
|
|
|
if (comptime std.meta.trait.hasFn("parse")(T)) return true;
|
|
|
|
if (T == bool) return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
pub fn deserializeString(allocator: std.mem.Allocator, comptime T: type, value: []const u8) !T {
|
|
|
|
if (comptime std.meta.trait.is(.Optional)(T)) {
|
|
|
|
if (value.len == 0) return null;
|
|
|
|
return try deserializeString(allocator, std.meta.Child(T), value);
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
if (T == []u8 or T == []const u8) return try util.deepClone(allocator, 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.hasFn("parse")(T)) return try T.parse(value);
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var buf: [64]u8 = undefined;
|
|
|
|
const lowered = std.ascii.lowerString(&buf, value);
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
if (T == bool) return bool_map.get(lowered) orelse return error.InvalidBool;
|
|
|
|
if (comptime std.meta.trait.is(.Enum)(T)) {
|
|
|
|
return std.meta.stringToEnum(T, lowered) orelse return error.InvalidEnumTag;
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
@compileError("Invalid type " ++ @typeName(T));
|
|
|
|
}
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
fn getStaticFieldList(comptime T: type, comptime prefix: FieldRef, comptime options: SerializationOptions) []const FieldRef {
|
2022-12-01 03:21:55 +00:00
|
|
|
comptime {
|
|
|
|
if (options.isScalar(T)) return &.{prefix};
|
2022-12-07 09:57:44 +00:00
|
|
|
if (std.meta.trait.is(.Optional)(T)) return getStaticFieldList(std.meta.Child(T), prefix, options);
|
|
|
|
if (std.meta.trait.isSlice(T) and !std.meta.trait.isZigString(T)) return &.{};
|
2022-12-01 03:21:55 +00:00
|
|
|
|
|
|
|
var fields: []const FieldRef = &.{};
|
|
|
|
|
|
|
|
for (std.meta.fields(T)) |f| {
|
2022-12-16 09:02:44 +00:00
|
|
|
const new_prefix = if (std.meta.trait.is(.Union)(T)) prefix else prefix ++ &[_][]const u8{f.name};
|
2022-12-01 03:21:55 +00:00
|
|
|
const F = f.field_type;
|
2022-12-07 09:57:44 +00:00
|
|
|
fields = fields ++ getStaticFieldList(F, new_prefix, options);
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
fn getDynamicFieldList(comptime T: type, comptime prefix: FieldRef, comptime options: SerializationOptions) []const DynamicField {
|
|
|
|
comptime {
|
|
|
|
if (options.isScalar(T)) return &.{};
|
|
|
|
if (std.meta.trait.is(.Optional)(T)) return getDynamicFieldList(std.meta.Child(T), prefix, options);
|
|
|
|
if (std.meta.trait.isSlice(T) and !std.meta.trait.isZigString(T)) return &.{
|
|
|
|
.{ .ref = prefix, .child_type = std.meta.Child(T) },
|
|
|
|
};
|
|
|
|
|
|
|
|
var fields: []const DynamicField = &.{};
|
|
|
|
|
|
|
|
for (std.meta.fields(T)) |f| {
|
2022-12-16 09:02:44 +00:00
|
|
|
const new_prefix = if (std.meta.trait.is(.Union)(T)) prefix else prefix ++ &[_][]const u8{f.name};
|
2022-12-07 09:57:44 +00:00
|
|
|
const F = f.field_type;
|
|
|
|
fields = fields ++ getDynamicFieldList(F, new_prefix, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const DynamicField = struct {
|
|
|
|
ref: FieldRef,
|
|
|
|
child_type: type,
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
pub const SerializationOptions = struct {
|
|
|
|
isScalar: fn (type) bool,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const default_options = SerializationOptions{
|
|
|
|
.isScalar = defaultIsScalar,
|
2022-12-01 03:21:55 +00:00
|
|
|
};
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
fn StaticIntermediary(comptime Result: type, comptime From: type, comptime options: SerializationOptions) type {
|
|
|
|
const field_refs = getStaticFieldList(Result, &.{}, options);
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-16 09:03:23 +00:00
|
|
|
// avert compiler crash by having at least one field
|
|
|
|
var fields = [_]std.builtin.Type.StructField{.{
|
|
|
|
.name = "__dummy",
|
|
|
|
.default_value = &{},
|
|
|
|
.field_type = void,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = 0,
|
|
|
|
}} ** (field_refs.len + 1);
|
|
|
|
|
|
|
|
var count: usize = 1;
|
|
|
|
outer: for (field_refs) |ref| {
|
|
|
|
const name = util.comptimeJoin(".", ref);
|
|
|
|
for (fields[0..count]) |f| if (std.mem.eql(u8, f.name, name)) continue :outer;
|
|
|
|
fields[count] = .{
|
|
|
|
.name = name,
|
2022-12-01 03:21:55 +00:00
|
|
|
.field_type = ?From,
|
|
|
|
.default_value = &@as(?From, null),
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(?From),
|
|
|
|
};
|
2022-12-16 09:03:23 +00:00
|
|
|
count += 1;
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
2022-12-16 09:03:23 +00:00
|
|
|
.fields = fields[0..count],
|
2022-12-01 03:21:55 +00:00
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
} });
|
|
|
|
}
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
fn DynamicIntermediary(comptime Result: type, comptime From: type, comptime options: SerializationOptions) type {
|
|
|
|
const field_refs = getDynamicFieldList(Result, &.{}, options);
|
|
|
|
|
2022-12-16 09:03:23 +00:00
|
|
|
var fields = [_]std.builtin.Type.StructField{.{
|
|
|
|
.name = "__dummy",
|
|
|
|
.default_value = &{},
|
|
|
|
.field_type = void,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = 0,
|
|
|
|
}} ** (field_refs.len + 1);
|
|
|
|
|
|
|
|
var count: usize = 1;
|
|
|
|
outer: for (field_refs) |ref| {
|
|
|
|
const name = util.comptimeJoin(".", ref.ref);
|
|
|
|
for (fields[0..count]) |f| if (std.mem.eql(u8, f.name, name)) continue :outer;
|
|
|
|
|
|
|
|
const T = std.ArrayListUnmanaged(Intermediary(ref.child_type, From, options));
|
|
|
|
fields[count] = .{
|
|
|
|
.name = name,
|
2022-12-07 09:57:44 +00:00
|
|
|
.default_value = &T{},
|
|
|
|
.field_type = T,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(T),
|
|
|
|
};
|
2022-12-16 09:03:23 +00:00
|
|
|
count += 1;
|
2022-12-07 09:57:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
2022-12-16 09:03:23 +00:00
|
|
|
.fields = fields[0..count],
|
2022-12-07 09:57:44 +00:00
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
} });
|
|
|
|
}
|
|
|
|
|
|
|
|
const SerializationInfo = struct {
|
|
|
|
max_slice_len: usize = 16,
|
|
|
|
};
|
|
|
|
|
|
|
|
fn getSerializationInfo(
|
|
|
|
comptime info: anytype,
|
|
|
|
comptime field_name: []const u8,
|
|
|
|
comptime info_key: std.meta.FieldEnum(SerializationInfo),
|
|
|
|
) std.meta.fieldInfo(SerializationInfo, info_key).field_type {
|
|
|
|
if (@hasDecl(info, "serialization_info") and
|
|
|
|
@hasDecl(info.serialization_info, field_name) and
|
|
|
|
@hasDecl(@field(info.serialization_info, field_name), @tagName(info_key)))
|
|
|
|
{
|
|
|
|
return @field(@field(info.serialization_info, field_name), @tagName(info_key));
|
|
|
|
} else return switch (info_key) {
|
|
|
|
.max_slice_len => 16,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn Intermediary(comptime Result: type, comptime From: type, comptime options: SerializationOptions) type {
|
|
|
|
return struct {
|
|
|
|
const StaticData = StaticIntermediary(Result, From, options);
|
|
|
|
const DynamicData = DynamicIntermediary(Result, From, options);
|
|
|
|
static: StaticData = .{},
|
|
|
|
dynamic: DynamicData = .{},
|
|
|
|
|
|
|
|
fn setSerializedField(self: *@This(), allocator: std.mem.Allocator, key: []const u8, value: From) !void {
|
|
|
|
var split = std.mem.split(u8, key, "[");
|
|
|
|
const first = split.first();
|
|
|
|
const rest = split.rest();
|
|
|
|
if (rest.len == 0) {
|
|
|
|
const field = std.meta.stringToEnum(std.meta.FieldEnum(StaticData), key) orelse return error.UnknownField;
|
|
|
|
inline for (comptime std.meta.fieldNames(StaticData)) |field_name| {
|
|
|
|
@setEvalBranchQuota(10000);
|
|
|
|
const f = comptime std.meta.stringToEnum(std.meta.FieldEnum(StaticData), field_name).?;
|
|
|
|
if (f != .__dummy and field == f) {
|
|
|
|
@field(self.static, field_name) = value;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unreachable;
|
|
|
|
} else {
|
|
|
|
split = std.mem.split(u8, rest, "]");
|
|
|
|
const idx_str = split.first();
|
|
|
|
const idx = try std.fmt.parseInt(usize, idx_str, 10);
|
|
|
|
var next = split.rest();
|
|
|
|
if (next.len == 0 or next[0] != '.') return error.UnknownField;
|
|
|
|
next = next[1..];
|
|
|
|
|
|
|
|
std.log.debug("{s} {s} {s}", .{ first, idx_str, next });
|
|
|
|
|
|
|
|
const field = std.meta.stringToEnum(std.meta.FieldEnum(DynamicData), first) orelse return error.UnknownField;
|
|
|
|
inline for (comptime std.meta.fieldNames(DynamicData)) |field_name| {
|
|
|
|
@setEvalBranchQuota(10000);
|
|
|
|
const f = comptime std.meta.stringToEnum(std.meta.FieldEnum(DynamicData), field_name).?;
|
|
|
|
if (f != .__dummy and field == f) {
|
|
|
|
const limits = getSerializationInfo(Result, field_name, .max_slice_len);
|
|
|
|
if (idx >= limits) return error.SliceTooLong;
|
|
|
|
const list = &@field(self.dynamic, field_name);
|
|
|
|
while (idx >= list.items.len) {
|
|
|
|
try list.append(allocator, .{});
|
|
|
|
}
|
|
|
|
|
|
|
|
try list.items[idx].setSerializedField(allocator, next, value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
pub fn Deserializer(comptime Result: type) type {
|
|
|
|
return DeserializerContext(Result, []const u8, struct {
|
|
|
|
const options = default_options;
|
|
|
|
fn deserializeScalar(_: @This(), alloc: std.mem.Allocator, comptime T: type, val: []const u8) !T {
|
|
|
|
return try deserializeString(alloc, T, val);
|
|
|
|
}
|
2022-12-01 03:21:55 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
pub fn DeserializerContext(comptime Result: type, comptime From: type, comptime Context: type) type {
|
2022-12-01 03:21:55 +00:00
|
|
|
return struct {
|
|
|
|
const Data = Intermediary(Result, From, Context.options);
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
arena: std.heap.ArenaAllocator,
|
|
|
|
|
2022-12-01 03:21:55 +00:00
|
|
|
data: Data = .{},
|
|
|
|
context: Context = .{},
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
pub fn init(alloc: std.mem.Allocator) @This() {
|
|
|
|
return .{ .arena = std.heap.ArenaAllocator.init(alloc) };
|
2022-12-03 05:49:17 +00:00
|
|
|
}
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
pub fn deinit(self: *@This()) void {
|
|
|
|
self.arena.deinit();
|
|
|
|
}
|
2022-12-03 05:49:17 +00:00
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
pub fn setSerializedField(self: *@This(), key: []const u8, value: From) !void {
|
|
|
|
try self.data.setSerializedField(self.arena.allocator(), key, value);
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
pub fn finishFree(_: *@This(), allocator: std.mem.Allocator, val: anytype) void {
|
|
|
|
util.deepFree(allocator, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn finish(self: *@This(), allocator: std.mem.Allocator) !Result {
|
2022-12-18 13:19:17 +00:00
|
|
|
return (try self.deserialize(allocator, Result, self.data, &.{}, true)) orelse
|
2022-12-16 10:37:04 +00:00
|
|
|
if (std.meta.fields(Result).len == 0)
|
|
|
|
return .{}
|
|
|
|
else
|
|
|
|
return error.MissingField;
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn getSerializedField(self: *@This(), comptime field_ref: FieldRef) ?From {
|
|
|
|
//inline for (comptime std.meta.fieldNames(Data)) |f| @compileLog(f.ptr);
|
|
|
|
return @field(self.data, util.comptimeJoin(".", field_ref));
|
|
|
|
}
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
fn deserializeFree(_: *@This(), allocator: std.mem.Allocator, val: anytype) void {
|
|
|
|
util.deepFree(allocator, val);
|
|
|
|
}
|
|
|
|
|
2022-12-16 09:04:01 +00:00
|
|
|
const DeserializeError = error{ ParseFailure, MissingField, DuplicateUnionMember, SparseSlice, OutOfMemory };
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
fn deserialize(
|
|
|
|
self: *@This(),
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
comptime T: type,
|
|
|
|
intermediary: anytype,
|
|
|
|
comptime field_ref: FieldRef,
|
2022-12-18 13:19:17 +00:00
|
|
|
allow_default: bool,
|
2022-12-16 09:04:01 +00:00
|
|
|
) DeserializeError!?T {
|
2022-12-01 05:11:54 +00:00
|
|
|
if (comptime Context.options.isScalar(T)) {
|
2022-12-07 09:57:44 +00:00
|
|
|
const val = @field(intermediary.static, util.comptimeJoin(".", field_ref));
|
2022-12-16 09:04:01 +00:00
|
|
|
return self.context.deserializeScalar(allocator, T, val orelse return null) catch return error.ParseFailure;
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (@typeInfo(T)) {
|
2022-12-16 09:04:01 +00:00
|
|
|
// At most one of any union field can be active at a time
|
2022-12-01 03:21:55 +00:00
|
|
|
.Union => |info| {
|
|
|
|
var result: ?T = null;
|
2022-12-01 05:11:54 +00:00
|
|
|
errdefer if (result) |v| self.deserializeFree(allocator, v);
|
2022-12-16 09:04:01 +00:00
|
|
|
var partial_match_found: bool = false;
|
2022-12-01 03:21:55 +00:00
|
|
|
inline for (info.fields) |field| {
|
|
|
|
const F = field.field_type;
|
2022-12-18 13:19:17 +00:00
|
|
|
const maybe_value = self.deserialize(allocator, F, intermediary, field_ref, false) catch |err| switch (err) {
|
2022-12-16 09:04:01 +00:00
|
|
|
error.MissingField => blk: {
|
|
|
|
partial_match_found = true;
|
|
|
|
break :blk @as(?F, null);
|
|
|
|
},
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
2022-12-01 03:21:55 +00:00
|
|
|
if (maybe_value) |value| {
|
2022-12-01 05:11:54 +00:00
|
|
|
errdefer self.deserializeFree(allocator, value);
|
2022-12-01 04:01:17 +00:00
|
|
|
if (result != null) return error.DuplicateUnionMember;
|
2022-12-01 03:21:55 +00:00
|
|
|
result = @unionInit(T, field.name, value);
|
|
|
|
}
|
|
|
|
}
|
2022-12-16 09:04:01 +00:00
|
|
|
if (partial_match_found and result == null) return error.MissingField;
|
2022-12-01 03:21:55 +00:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
.Struct => |info| {
|
|
|
|
var result: T = undefined;
|
|
|
|
|
|
|
|
var any_explicit = false;
|
|
|
|
var any_missing = false;
|
2022-12-01 05:11:54 +00:00
|
|
|
var fields_alloced = [1]bool{false} ** info.fields.len;
|
|
|
|
errdefer inline for (info.fields) |field, i| {
|
|
|
|
if (fields_alloced[i]) self.deserializeFree(allocator, @field(result, field.name));
|
|
|
|
};
|
|
|
|
inline for (info.fields) |field, i| {
|
2022-12-01 03:21:55 +00:00
|
|
|
const F = field.field_type;
|
|
|
|
const new_field_ref = field_ref ++ &[_][]const u8{field.name};
|
2022-12-18 13:19:17 +00:00
|
|
|
const maybe_value = try self.deserialize(allocator, F, intermediary, new_field_ref, false);
|
2022-12-01 03:21:55 +00:00
|
|
|
if (maybe_value) |v| {
|
|
|
|
@field(result, field.name) = v;
|
2022-12-01 05:11:54 +00:00
|
|
|
fields_alloced[i] = true;
|
2022-12-01 03:21:55 +00:00
|
|
|
any_explicit = true;
|
|
|
|
} else if (field.default_value) |ptr| {
|
|
|
|
if (@sizeOf(F) != 0) {
|
2022-12-01 05:11:54 +00:00
|
|
|
const cast_ptr = @ptrCast(*const F, @alignCast(field.alignment, ptr));
|
|
|
|
@field(result, field.name) = try util.deepClone(allocator, cast_ptr.*);
|
|
|
|
fields_alloced[i] = true;
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
any_missing = true;
|
|
|
|
}
|
|
|
|
}
|
2022-12-10 06:32:34 +00:00
|
|
|
if (any_missing and any_explicit) return error.MissingField;
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-18 13:19:17 +00:00
|
|
|
if (!any_explicit and !allow_default) {
|
2022-12-10 06:32:34 +00:00
|
|
|
inline for (info.fields) |field, i| {
|
|
|
|
if (fields_alloced[i]) self.deserializeFree(allocator, @field(result, field.name));
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2022-12-01 03:21:55 +00:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
2022-12-07 09:57:44 +00:00
|
|
|
.Pointer => |info| switch (info.size) {
|
|
|
|
.Slice => {
|
|
|
|
const name = comptime util.comptimeJoin(".", field_ref);
|
|
|
|
const data = @field(self.data.dynamic, name);
|
|
|
|
|
|
|
|
const result = try allocator.alloc(info.child, data.items.len);
|
|
|
|
errdefer allocator.free(result);
|
|
|
|
var count: usize = 0;
|
|
|
|
errdefer for (result[0..count]) |res| util.deepFree(allocator, res);
|
|
|
|
for (data.items) |sub, i| {
|
2022-12-18 13:19:17 +00:00
|
|
|
result[i] = (try self.deserialize(allocator, info.child, sub, &.{}, false)) orelse return error.SparseSlice;
|
2022-12-07 09:57:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
else => @compileError("Unsupported type"),
|
|
|
|
},
|
|
|
|
|
2022-12-01 03:21:55 +00:00
|
|
|
// Specifically non-scalar optionals
|
2022-12-18 13:19:17 +00:00
|
|
|
.Optional => |info| return try self.deserialize(allocator, info.child, intermediary, field_ref, allow_default),
|
2022-12-01 03:21:55 +00:00
|
|
|
|
|
|
|
else => @compileError("Unsupported type"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-06 09:43:03 +00:00
|
|
|
pub const bool_map = std.ComptimeStringMap(bool, .{
|
2022-12-01 03:21:55 +00:00
|
|
|
.{ "true", true },
|
|
|
|
.{ "t", true },
|
|
|
|
.{ "yes", true },
|
|
|
|
.{ "y", true },
|
|
|
|
.{ "1", true },
|
|
|
|
|
|
|
|
.{ "false", false },
|
|
|
|
.{ "f", false },
|
|
|
|
.{ "no", false },
|
|
|
|
.{ "n", false },
|
|
|
|
.{ "0", false },
|
|
|
|
});
|
|
|
|
|
2022-12-01 04:01:17 +00:00
|
|
|
test "Deserializer" {
|
2022-12-01 03:21:55 +00:00
|
|
|
|
2022-12-01 04:01:17 +00:00
|
|
|
// Happy case - simple
|
2022-12-01 03:21:55 +00:00
|
|
|
{
|
2022-12-01 05:11:54 +00:00
|
|
|
const T = struct { foo: []const u8, bar: bool };
|
2022-12-01 04:01:17 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 03:21:55 +00:00
|
|
|
try ds.setSerializedField("foo", "123");
|
|
|
|
try ds.setSerializedField("bar", "true");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
const val = try ds.finish(std.testing.allocator);
|
|
|
|
defer ds.finishFree(std.testing.allocator, val);
|
|
|
|
try util.testing.expectDeepEqual(T{ .foo = "123", .bar = true }, val);
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
2022-12-01 04:01:17 +00:00
|
|
|
|
|
|
|
// Returns error if nonexistent field set
|
|
|
|
{
|
2022-12-01 05:11:54 +00:00
|
|
|
const T = struct { foo: []const u8, bar: bool };
|
2022-12-01 04:01:17 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 04:01:17 +00:00
|
|
|
try std.testing.expectError(error.UnknownField, ds.setSerializedField("baz", "123"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Substruct dereferencing
|
|
|
|
{
|
|
|
|
const T = struct {
|
|
|
|
foo: struct { bar: bool, baz: bool },
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 04:01:17 +00:00
|
|
|
try ds.setSerializedField("foo.bar", "true");
|
|
|
|
try ds.setSerializedField("foo.baz", "true");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
const val = try ds.finish(std.testing.allocator);
|
|
|
|
defer ds.finishFree(std.testing.allocator, val);
|
|
|
|
try util.testing.expectDeepEqual(T{ .foo = .{ .bar = true, .baz = true } }, val);
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Union embedding
|
|
|
|
{
|
|
|
|
const T = struct {
|
|
|
|
foo: union(enum) { bar: bool, baz: bool },
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 04:01:17 +00:00
|
|
|
try ds.setSerializedField("bar", "true");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
const val = try ds.finish(std.testing.allocator);
|
|
|
|
defer ds.finishFree(std.testing.allocator, val);
|
|
|
|
try util.testing.expectDeepEqual(T{ .foo = .{ .bar = true } }, val);
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns error if multiple union fields specified
|
|
|
|
{
|
|
|
|
const T = struct {
|
|
|
|
foo: union(enum) { bar: bool, baz: bool },
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 04:01:17 +00:00
|
|
|
try ds.setSerializedField("bar", "true");
|
|
|
|
try ds.setSerializedField("baz", "true");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
try std.testing.expectError(error.DuplicateUnionMember, ds.finish(std.testing.allocator));
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Uses default values if fields aren't provided
|
|
|
|
{
|
2022-12-01 05:11:54 +00:00
|
|
|
const T = struct { foo: []const u8 = "123", bar: bool = true };
|
2022-12-01 04:01:17 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
|
|
|
|
|
|
|
const val = try ds.finish(std.testing.allocator);
|
|
|
|
defer ds.finishFree(std.testing.allocator, val);
|
|
|
|
try util.testing.expectDeepEqual(T{ .foo = "123", .bar = true }, val);
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns an error if fields aren't provided and no default exists
|
2022-12-01 03:21:55 +00:00
|
|
|
{
|
2022-12-01 05:11:54 +00:00
|
|
|
const T = struct { foo: []const u8, bar: bool };
|
2022-12-01 04:01:17 +00:00
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 03:21:55 +00:00
|
|
|
try ds.setSerializedField("foo", "123");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
try std.testing.expectError(error.MissingField, ds.finish(std.testing.allocator));
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-01 04:01:17 +00:00
|
|
|
// Handles optional containers
|
|
|
|
{
|
|
|
|
const T = struct {
|
|
|
|
foo: ?struct { bar: usize = 3, baz: usize } = null,
|
|
|
|
qux: ?union(enum) { quux: usize } = null,
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
|
|
|
|
|
|
|
const val = try ds.finish(std.testing.allocator);
|
|
|
|
defer ds.finishFree(std.testing.allocator, val);
|
|
|
|
try util.testing.expectDeepEqual(T{ .foo = null, .qux = null }, val);
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const T = struct {
|
|
|
|
foo: ?struct { bar: usize = 3, baz: usize } = null,
|
|
|
|
qux: ?union(enum) { quux: usize } = null,
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 04:01:17 +00:00
|
|
|
try ds.setSerializedField("foo.baz", "3");
|
|
|
|
try ds.setSerializedField("quux", "3");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
const val = try ds.finish(std.testing.allocator);
|
|
|
|
defer ds.finishFree(std.testing.allocator, val);
|
|
|
|
try util.testing.expectDeepEqual(T{ .foo = .{ .bar = 3, .baz = 3 }, .qux = .{ .quux = 3 } }, val);
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const T = struct {
|
|
|
|
foo: ?struct { bar: usize = 3, baz: usize } = null,
|
|
|
|
qux: ?union(enum) { quux: usize } = null,
|
|
|
|
};
|
|
|
|
|
2022-12-01 05:11:54 +00:00
|
|
|
var ds = Deserializer(T){};
|
2022-12-01 04:01:17 +00:00
|
|
|
try ds.setSerializedField("foo.bar", "3");
|
|
|
|
try ds.setSerializedField("quux", "3");
|
2022-12-01 05:11:54 +00:00
|
|
|
|
|
|
|
try std.testing.expectError(error.MissingField, ds.finish(std.testing.allocator));
|
2022-12-01 04:01:17 +00:00
|
|
|
}
|
2022-12-01 03:21:55 +00:00
|
|
|
}
|