mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
* Adds startTupleField/startStructField, makes pattern in print targets less verbose * Makes some enums into strings * Start/finish renamed to begin/end I feel bad changing this, but I don't know why I named them this way in the first place. Begin/end is consistent with the json API, and with other APIs in the wild that follow this pattern. Better to change now than later.
2426 lines
82 KiB
Zig
2426 lines
82 KiB
Zig
//! ZON can be serialized with `serialize`.
|
|
//!
|
|
//! The following functions are provided for serializing recursive types:
|
|
//! * `serializeMaxDepth`
|
|
//! * `serializeArbitraryDepth`
|
|
//!
|
|
//! For additional control over serialization, see `Serializer`.
|
|
//!
|
|
//! The following types and any types that contain them may not be serialized:
|
|
//! * `type`
|
|
//! * `void`, except as a union payload
|
|
//! * `noreturn`
|
|
//! * Error sets/error unions
|
|
//! * Untagged unions
|
|
//! * Many-pointers or C-pointers
|
|
//! * Opaque types, including `anyopaque`
|
|
//! * Async frame types, including `anyframe` and `anyframe->T`
|
|
//! * Functions
|
|
//!
|
|
//! All other types are valid. Unsupported types will fail to serialize at compile time. Pointers
|
|
//! are followed.
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
|
|
/// Options for `serialize`.
|
|
pub const SerializeOptions = struct {
|
|
/// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style.
|
|
whitespace: bool = true,
|
|
/// Determines when to emit Unicode code point literals as opposed to integer literals.
|
|
emit_codepoint_literals: EmitCodepointLiterals = .never,
|
|
/// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers.
|
|
/// Otherwise they are serialized as string literals.
|
|
emit_strings_as_containers: bool = false,
|
|
/// If false, struct fields are not written if they are equal to their default value. Comparison
|
|
/// is done by `std.meta.eql`.
|
|
emit_default_optional_fields: bool = true,
|
|
};
|
|
|
|
/// Serialize the given value as ZON.
|
|
///
|
|
/// It is asserted at comptime that `@TypeOf(val)` is not a recursive type.
|
|
pub fn serialize(
|
|
val: anytype,
|
|
options: SerializeOptions,
|
|
writer: anytype,
|
|
) @TypeOf(writer).Error!void {
|
|
var sz = serializer(writer, .{
|
|
.whitespace = options.whitespace,
|
|
});
|
|
try sz.value(val, .{
|
|
.emit_codepoint_literals = options.emit_codepoint_literals,
|
|
.emit_strings_as_containers = options.emit_strings_as_containers,
|
|
.emit_default_optional_fields = options.emit_default_optional_fields,
|
|
});
|
|
}
|
|
|
|
/// Like `serialize`, but recursive types are allowed.
|
|
///
|
|
/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. Every nested value adds one to a
|
|
/// value's depth.
|
|
pub fn serializeMaxDepth(
|
|
val: anytype,
|
|
options: SerializeOptions,
|
|
writer: anytype,
|
|
depth: usize,
|
|
) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void {
|
|
var sz = serializer(writer, .{
|
|
.whitespace = options.whitespace,
|
|
});
|
|
try sz.valueMaxDepth(val, .{
|
|
.emit_codepoint_literals = options.emit_codepoint_literals,
|
|
.emit_strings_as_containers = options.emit_strings_as_containers,
|
|
.emit_default_optional_fields = options.emit_default_optional_fields,
|
|
}, depth);
|
|
}
|
|
|
|
/// Like `serialize`, but recursive types are allowed.
|
|
///
|
|
/// It is the caller's responsibility to ensure that `val` does not contain cycles.
|
|
pub fn serializeArbitraryDepth(
|
|
val: anytype,
|
|
options: SerializeOptions,
|
|
writer: anytype,
|
|
) @TypeOf(writer).Error!void {
|
|
var sz = serializer(writer, .{
|
|
.whitespace = options.whitespace,
|
|
});
|
|
try sz.valueArbitraryDepth(val, .{
|
|
.emit_codepoint_literals = options.emit_codepoint_literals,
|
|
.emit_strings_as_containers = options.emit_strings_as_containers,
|
|
.emit_default_optional_fields = options.emit_default_optional_fields,
|
|
});
|
|
}
|
|
|
|
fn typeIsRecursive(comptime T: type) bool {
|
|
return comptime typeIsRecursiveImpl(T, &.{});
|
|
}
|
|
|
|
fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []const type) bool {
|
|
for (prev_visited) |V| {
|
|
if (V == T) return true;
|
|
}
|
|
const visited = prev_visited ++ .{T};
|
|
|
|
return switch (@typeInfo(T)) {
|
|
.pointer => |pointer| typeIsRecursiveImpl(pointer.child, visited),
|
|
.optional => |optional| typeIsRecursiveImpl(optional.child, visited),
|
|
.array => |array| typeIsRecursiveImpl(array.child, visited),
|
|
.vector => |vector| typeIsRecursiveImpl(vector.child, visited),
|
|
.@"struct" => |@"struct"| for (@"struct".fields) |field| {
|
|
if (typeIsRecursiveImpl(field.type, visited)) break true;
|
|
} else false,
|
|
.@"union" => |@"union"| inline for (@"union".fields) |field| {
|
|
if (typeIsRecursiveImpl(field.type, visited)) break true;
|
|
} else false,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
fn canSerializeType(T: type) bool {
|
|
comptime return canSerializeTypeInner(T, &.{}, false);
|
|
}
|
|
|
|
fn canSerializeTypeInner(
|
|
T: type,
|
|
/// Visited structs and unions, to avoid infinite recursion.
|
|
/// Tracking more types is unnecessary, and a little complex due to optional nesting.
|
|
visited: []const type,
|
|
parent_is_optional: bool,
|
|
) bool {
|
|
return switch (@typeInfo(T)) {
|
|
.bool,
|
|
.int,
|
|
.float,
|
|
.comptime_float,
|
|
.comptime_int,
|
|
.null,
|
|
.enum_literal,
|
|
=> true,
|
|
|
|
.noreturn,
|
|
.void,
|
|
.type,
|
|
.undefined,
|
|
.error_union,
|
|
.error_set,
|
|
.@"fn",
|
|
.frame,
|
|
.@"anyframe",
|
|
.@"opaque",
|
|
=> false,
|
|
|
|
.@"enum" => |@"enum"| @"enum".is_exhaustive,
|
|
|
|
.pointer => |pointer| switch (pointer.size) {
|
|
.one => canSerializeTypeInner(pointer.child, visited, parent_is_optional),
|
|
.slice => canSerializeTypeInner(pointer.child, visited, false),
|
|
.many, .c => false,
|
|
},
|
|
|
|
.optional => |optional| if (parent_is_optional)
|
|
false
|
|
else
|
|
canSerializeTypeInner(optional.child, visited, true),
|
|
|
|
.array => |array| canSerializeTypeInner(array.child, visited, false),
|
|
.vector => |vector| canSerializeTypeInner(vector.child, visited, false),
|
|
|
|
.@"struct" => |@"struct"| {
|
|
for (visited) |V| if (T == V) return true;
|
|
const new_visited = visited ++ .{T};
|
|
for (@"struct".fields) |field| {
|
|
if (!canSerializeTypeInner(field.type, new_visited, false)) return false;
|
|
}
|
|
return true;
|
|
},
|
|
.@"union" => |@"union"| {
|
|
for (visited) |V| if (T == V) return true;
|
|
const new_visited = visited ++ .{T};
|
|
if (@"union".tag_type == null) return false;
|
|
for (@"union".fields) |field| {
|
|
if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
};
|
|
}
|
|
|
|
fn isNestedOptional(T: type) bool {
|
|
comptime switch (@typeInfo(T)) {
|
|
.optional => |optional| return isNestedOptionalInner(optional.child),
|
|
else => return false,
|
|
};
|
|
}
|
|
|
|
fn isNestedOptionalInner(T: type) bool {
|
|
switch (@typeInfo(T)) {
|
|
.pointer => |pointer| {
|
|
if (pointer.size == .one) {
|
|
return isNestedOptionalInner(pointer.child);
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
.optional => return true,
|
|
else => return false,
|
|
}
|
|
}
|
|
|
|
test "std.zon stringify canSerializeType" {
|
|
try std.testing.expect(!comptime canSerializeType(void));
|
|
try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 }));
|
|
try std.testing.expect(!comptime canSerializeType(struct { error{foo} }));
|
|
try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 }));
|
|
try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8)));
|
|
try std.testing.expect(!comptime canSerializeType(*?[*c]u8));
|
|
try std.testing.expect(!comptime canSerializeType(enum(u8) { _ }));
|
|
try std.testing.expect(!comptime canSerializeType(union { foo: void }));
|
|
try std.testing.expect(comptime canSerializeType(union(enum) { foo: void }));
|
|
try std.testing.expect(comptime canSerializeType(comptime_float));
|
|
try std.testing.expect(comptime canSerializeType(comptime_int));
|
|
try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null }));
|
|
try std.testing.expect(comptime canSerializeType(@TypeOf(.foo)));
|
|
try std.testing.expect(comptime canSerializeType(?u8));
|
|
try std.testing.expect(comptime canSerializeType(*?*u8));
|
|
try std.testing.expect(comptime canSerializeType(?struct {
|
|
foo: ?struct {
|
|
?union(enum) {
|
|
a: ?@Vector(0, ?*u8),
|
|
},
|
|
?struct {
|
|
f: ?[]?u8,
|
|
},
|
|
},
|
|
}));
|
|
try std.testing.expect(!comptime canSerializeType(??u8));
|
|
try std.testing.expect(!comptime canSerializeType(?*?u8));
|
|
try std.testing.expect(!comptime canSerializeType(*?*?*u8));
|
|
try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 }));
|
|
try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 }));
|
|
try std.testing.expect(comptime canSerializeType(struct { comptime_int }));
|
|
try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo }));
|
|
const Recursive = struct { foo: ?*@This() };
|
|
try std.testing.expect(comptime canSerializeType(Recursive));
|
|
|
|
// Make sure we validate nested optional before we early out due to already having seen
|
|
// a type recursion!
|
|
try std.testing.expect(!comptime canSerializeType(struct {
|
|
add_to_visited: ?u8,
|
|
retrieve_from_visited: ??u8,
|
|
}));
|
|
}
|
|
|
|
test "std.zon typeIsRecursive" {
|
|
try std.testing.expect(!typeIsRecursive(bool));
|
|
try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 }));
|
|
try std.testing.expect(!typeIsRecursive(struct { i32, i32 }));
|
|
try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() }));
|
|
try std.testing.expect(typeIsRecursive(struct {
|
|
a: struct {
|
|
const A = @This();
|
|
b: struct {
|
|
c: *struct {
|
|
a: ?A,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
try std.testing.expect(typeIsRecursive(struct {
|
|
a: [3]*@This(),
|
|
}));
|
|
try std.testing.expect(typeIsRecursive(struct {
|
|
a: union { a: i32, b: *@This() },
|
|
}));
|
|
}
|
|
|
|
fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void {
|
|
if (depth == 0) return error.ExceededMaxDepth;
|
|
const child_depth = depth - 1;
|
|
|
|
switch (@typeInfo(@TypeOf(val))) {
|
|
.pointer => |pointer| switch (pointer.size) {
|
|
.one => try checkValueDepth(val.*, child_depth),
|
|
.slice => for (val) |item| {
|
|
try checkValueDepth(item, child_depth);
|
|
},
|
|
.c, .many => {},
|
|
},
|
|
.array => for (val) |item| {
|
|
try checkValueDepth(item, child_depth);
|
|
},
|
|
.@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| {
|
|
try checkValueDepth(@field(val, field_info.name), child_depth);
|
|
},
|
|
.@"union" => |@"union"| if (@"union".tag_type == null) {
|
|
return;
|
|
} else switch (val) {
|
|
inline else => |payload| {
|
|
return checkValueDepth(payload, child_depth);
|
|
},
|
|
},
|
|
.optional => if (val) |inner| try checkValueDepth(inner, child_depth),
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
fn expectValueDepthEquals(expected: usize, value: anytype) !void {
|
|
try checkValueDepth(value, expected);
|
|
try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1));
|
|
}
|
|
|
|
test "std.zon checkValueDepth" {
|
|
try expectValueDepthEquals(1, 10);
|
|
try expectValueDepthEquals(2, .{ .x = 1, .y = 2 });
|
|
try expectValueDepthEquals(2, .{ 1, 2 });
|
|
try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } });
|
|
try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 });
|
|
try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } });
|
|
try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 });
|
|
try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 });
|
|
try expectValueDepthEquals(2, @as(?u32, 1));
|
|
try expectValueDepthEquals(1, @as(?u32, null));
|
|
try expectValueDepthEquals(1, null);
|
|
try expectValueDepthEquals(2, &1);
|
|
try expectValueDepthEquals(3, &@as(?u32, 1));
|
|
|
|
const Union = union(enum) {
|
|
x: u32,
|
|
y: struct { x: u32 },
|
|
};
|
|
try expectValueDepthEquals(2, Union{ .x = 1 });
|
|
try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } });
|
|
|
|
const Recurse = struct { r: ?*const @This() };
|
|
try expectValueDepthEquals(2, Recurse{ .r = null });
|
|
try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } });
|
|
try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } });
|
|
|
|
try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 }));
|
|
try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
|
|
}
|
|
|
|
/// Options for `Serializer`.
|
|
pub const SerializerOptions = struct {
|
|
/// If false, only syntactically necessary whitespace is emitted.
|
|
whitespace: bool = true,
|
|
};
|
|
|
|
/// Determines when to emit Unicode code point literals as opposed to integer literals.
|
|
pub const EmitCodepointLiterals = enum {
|
|
/// Never emit Unicode code point literals.
|
|
never,
|
|
/// Emit Unicode code point literals for any `u8` in the printable ASCII range.
|
|
printable_ascii,
|
|
/// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer
|
|
/// whose value is a valid non-surrogate code point.
|
|
always,
|
|
|
|
/// If the value should be emitted as a Unicode codepoint, return it as a u21.
|
|
fn emitAsCodepoint(self: @This(), val: anytype) ?u21 {
|
|
// Rule out incompatible integer types
|
|
switch (@typeInfo(@TypeOf(val))) {
|
|
.int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) {
|
|
return null;
|
|
},
|
|
.comptime_int => {},
|
|
else => comptime unreachable,
|
|
}
|
|
|
|
// Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted
|
|
// to a u21 if it should.
|
|
switch (self) {
|
|
.always => {
|
|
const c = std.math.cast(u21, val) orelse return null;
|
|
if (!std.unicode.utf8ValidCodepoint(c)) return null;
|
|
return c;
|
|
},
|
|
.printable_ascii => {
|
|
const c = std.math.cast(u8, val) orelse return null;
|
|
if (!std.ascii.isPrint(c)) return null;
|
|
return c;
|
|
},
|
|
.never => {
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Options for serialization of an individual value.
|
|
///
|
|
/// See `SerializeOptions` for more information on these options.
|
|
pub const ValueOptions = struct {
|
|
emit_codepoint_literals: EmitCodepointLiterals = .never,
|
|
emit_strings_as_containers: bool = false,
|
|
emit_default_optional_fields: bool = true,
|
|
};
|
|
|
|
/// Options for manual serialization of container types.
|
|
pub const SerializeContainerOptions = struct {
|
|
/// The whitespace style that should be used for this container. Ignored if whitespace is off.
|
|
whitespace_style: union(enum) {
|
|
/// If true, wrap every field. If false do not.
|
|
wrap: bool,
|
|
/// Automatically decide whether to wrap or not based on the number of fields. Following
|
|
/// the standard rule of thumb, containers with more than two fields are wrapped.
|
|
fields: usize,
|
|
} = .{ .wrap = true },
|
|
|
|
fn shouldWrap(self: SerializeContainerOptions) bool {
|
|
return switch (self.whitespace_style) {
|
|
.wrap => |wrap| wrap,
|
|
.fields => |fields| fields > 2,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Lower level control over serialization, you can create a new instance with `serializer`.
|
|
///
|
|
/// Useful when you want control over which fields are serialized, how they're represented,
|
|
/// or want to write a ZON object that does not exist in memory.
|
|
///
|
|
/// You can serialize values with `value`. To serialize recursive types, the following are provided:
|
|
/// * `valueMaxDepth`
|
|
/// * `valueArbitraryDepth`
|
|
///
|
|
/// You can also serialize values using specific notations:
|
|
/// * `int`
|
|
/// * `float`
|
|
/// * `codePoint`
|
|
/// * `tuple`
|
|
/// * `tupleMaxDepth`
|
|
/// * `tupleArbitraryDepth`
|
|
/// * `string`
|
|
/// * `multilineString`
|
|
///
|
|
/// For manual serialization of containers, see:
|
|
/// * `beginStruct`
|
|
/// * `beginTuple`
|
|
///
|
|
/// # Example
|
|
/// ```zig
|
|
/// var sz = serializer(writer, .{});
|
|
/// var vec2 = try sz.beginStruct(.{});
|
|
/// try vec2.field("x", 1.5, .{});
|
|
/// try vec2.fieldPrefix();
|
|
/// try sz.value(2.5);
|
|
/// try vec2.end();
|
|
/// ```
|
|
pub fn Serializer(Writer: type) type {
|
|
return struct {
|
|
const Self = @This();
|
|
|
|
options: SerializerOptions,
|
|
indent_level: u8,
|
|
writer: Writer,
|
|
|
|
/// Initialize a serializer.
|
|
fn init(writer: Writer, options: SerializerOptions) Self {
|
|
return .{
|
|
.options = options,
|
|
.writer = writer,
|
|
.indent_level = 0,
|
|
};
|
|
}
|
|
|
|
/// Serialize a value, similar to `serialize`.
|
|
pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
|
|
comptime assert(!typeIsRecursive(@TypeOf(val)));
|
|
return self.valueArbitraryDepth(val, options);
|
|
}
|
|
|
|
/// Serialize a value, similar to `serializeMaxDepth`.
|
|
pub fn valueMaxDepth(
|
|
self: *Self,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
depth: usize,
|
|
) (Writer.Error || error{ExceededMaxDepth})!void {
|
|
try checkValueDepth(val, depth);
|
|
return self.valueArbitraryDepth(val, options);
|
|
}
|
|
|
|
/// Serialize a value, similar to `serializeArbitraryDepth`.
|
|
pub fn valueArbitraryDepth(
|
|
self: *Self,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
comptime assert(canSerializeType(@TypeOf(val)));
|
|
switch (@typeInfo(@TypeOf(val))) {
|
|
.int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
|
|
self.codePoint(c) catch |err| switch (err) {
|
|
error.InvalidCodepoint => unreachable, // Already validated
|
|
else => |e| return e,
|
|
};
|
|
} else {
|
|
try self.int(val);
|
|
},
|
|
.float, .comptime_float => try self.float(val),
|
|
.bool, .null => try std.fmt.format(self.writer, "{}", .{val}),
|
|
.enum_literal => try self.ident(@tagName(val)),
|
|
.@"enum" => try self.ident(@tagName(val)),
|
|
.pointer => |pointer| {
|
|
// Try to serialize as a string
|
|
const item: ?type = switch (@typeInfo(pointer.child)) {
|
|
.array => |array| array.child,
|
|
else => if (pointer.size == .slice) pointer.child else null,
|
|
};
|
|
if (item == u8 and
|
|
(pointer.sentinel() == null or pointer.sentinel() == 0) and
|
|
!options.emit_strings_as_containers)
|
|
{
|
|
return try self.string(val);
|
|
}
|
|
|
|
// Serialize as either a tuple or as the child type
|
|
switch (pointer.size) {
|
|
.slice => try self.tupleImpl(val, options),
|
|
.one => try self.valueArbitraryDepth(val.*, options),
|
|
else => comptime unreachable,
|
|
}
|
|
},
|
|
.array => {
|
|
var container = try self.beginTuple(
|
|
.{ .whitespace_style = .{ .fields = val.len } },
|
|
);
|
|
for (val) |item_val| {
|
|
try container.fieldArbitraryDepth(item_val, options);
|
|
}
|
|
try container.end();
|
|
},
|
|
.@"struct" => |@"struct"| if (@"struct".is_tuple) {
|
|
var container = try self.beginTuple(
|
|
.{ .whitespace_style = .{ .fields = @"struct".fields.len } },
|
|
);
|
|
inline for (val) |field_value| {
|
|
try container.fieldArbitraryDepth(field_value, options);
|
|
}
|
|
try container.end();
|
|
} else {
|
|
// Decide which fields to emit
|
|
const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
|
|
break :b .{ @"struct".fields.len, @splat(false) };
|
|
} else b: {
|
|
var fields = @"struct".fields.len;
|
|
var skipped: [@"struct".fields.len]bool = @splat(false);
|
|
inline for (@"struct".fields, &skipped) |field_info, *skip| {
|
|
if (field_info.default_value_ptr) |ptr| {
|
|
const default: *const field_info.type = @ptrCast(@alignCast(ptr));
|
|
const field_value = @field(val, field_info.name);
|
|
if (std.meta.eql(field_value, default.*)) {
|
|
skip.* = true;
|
|
fields -= 1;
|
|
}
|
|
}
|
|
}
|
|
break :b .{ fields, skipped };
|
|
};
|
|
|
|
// Emit those fields
|
|
var container = try self.beginStruct(
|
|
.{ .whitespace_style = .{ .fields = fields } },
|
|
);
|
|
inline for (@"struct".fields, skipped) |field_info, skip| {
|
|
if (!skip) {
|
|
try container.fieldArbitraryDepth(
|
|
field_info.name,
|
|
@field(val, field_info.name),
|
|
options,
|
|
);
|
|
}
|
|
}
|
|
try container.end();
|
|
},
|
|
.@"union" => |@"union"| {
|
|
comptime assert(@"union".tag_type != null);
|
|
switch (val) {
|
|
inline else => |pl, tag| if (@TypeOf(pl) == void)
|
|
try self.writer.print(".{s}", .{@tagName(tag)})
|
|
else {
|
|
var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
|
|
|
|
try container.fieldArbitraryDepth(
|
|
@tagName(tag),
|
|
pl,
|
|
options,
|
|
);
|
|
|
|
try container.end();
|
|
},
|
|
}
|
|
},
|
|
.optional => if (val) |inner| {
|
|
try self.valueArbitraryDepth(inner, options);
|
|
} else {
|
|
try self.writer.writeAll("null");
|
|
},
|
|
.vector => |vector| {
|
|
var container = try self.beginTuple(
|
|
.{ .whitespace_style = .{ .fields = vector.len } },
|
|
);
|
|
for (0..vector.len) |i| {
|
|
try container.fieldArbitraryDepth(val[i], options);
|
|
}
|
|
try container.end();
|
|
},
|
|
|
|
else => comptime unreachable,
|
|
}
|
|
}
|
|
|
|
/// Serialize an integer.
|
|
pub fn int(self: *Self, val: anytype) Writer.Error!void {
|
|
try std.fmt.formatInt(val, 10, .lower, .{}, self.writer);
|
|
}
|
|
|
|
/// Serialize a float.
|
|
pub fn float(self: *Self, val: anytype) Writer.Error!void {
|
|
switch (@typeInfo(@TypeOf(val))) {
|
|
.float => if (std.math.isNan(val)) {
|
|
return self.writer.writeAll("nan");
|
|
} else if (std.math.isPositiveInf(val)) {
|
|
return self.writer.writeAll("inf");
|
|
} else if (std.math.isNegativeInf(val)) {
|
|
return self.writer.writeAll("-inf");
|
|
} else {
|
|
try std.fmt.format(self.writer, "{d}", .{val});
|
|
},
|
|
.comptime_float => try std.fmt.format(self.writer, "{d}", .{val}),
|
|
else => comptime unreachable,
|
|
}
|
|
}
|
|
|
|
/// Serialize `name` as an identifier prefixed with `.`.
|
|
///
|
|
/// Escapes the identifier if necessary.
|
|
pub fn ident(self: *Self, name: []const u8) Writer.Error!void {
|
|
try self.writer.print(".{p_}", .{std.zig.fmtId(name)});
|
|
}
|
|
|
|
/// Serialize `val` as a Unicode codepoint.
|
|
///
|
|
/// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
|
|
pub fn codePoint(
|
|
self: *Self,
|
|
val: u21,
|
|
) (Writer.Error || error{InvalidCodepoint})!void {
|
|
var buf: [8]u8 = undefined;
|
|
const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint;
|
|
const str = buf[0..len];
|
|
try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)});
|
|
}
|
|
|
|
/// Like `value`, but always serializes `val` as a tuple.
|
|
///
|
|
/// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
|
|
pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
|
|
comptime assert(!typeIsRecursive(@TypeOf(val)));
|
|
try self.tupleArbitraryDepth(val, options);
|
|
}
|
|
|
|
/// Like `tuple`, but recursive types are allowed.
|
|
///
|
|
/// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
|
|
pub fn tupleMaxDepth(
|
|
self: *Self,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
depth: usize,
|
|
) (Writer.Error || error{ExceededMaxDepth})!void {
|
|
try checkValueDepth(val, depth);
|
|
try self.tupleArbitraryDepth(val, options);
|
|
}
|
|
|
|
/// Like `tuple`, but recursive types are allowed.
|
|
///
|
|
/// It is the caller's responsibility to ensure that `val` does not contain cycles.
|
|
pub fn tupleArbitraryDepth(
|
|
self: *Self,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
try self.tupleImpl(val, options);
|
|
}
|
|
|
|
fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
|
|
comptime assert(canSerializeType(@TypeOf(val)));
|
|
switch (@typeInfo(@TypeOf(val))) {
|
|
.@"struct" => {
|
|
var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
|
|
inline for (val) |item_val| {
|
|
try container.fieldArbitraryDepth(item_val, options);
|
|
}
|
|
try container.end();
|
|
},
|
|
.pointer, .array => {
|
|
var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
|
|
for (val) |item_val| {
|
|
try container.fieldArbitraryDepth(item_val, options);
|
|
}
|
|
try container.end();
|
|
},
|
|
else => comptime unreachable,
|
|
}
|
|
}
|
|
|
|
/// Like `value`, but always serializes `val` as a string.
|
|
pub fn string(self: *Self, val: []const u8) Writer.Error!void {
|
|
try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)});
|
|
}
|
|
|
|
/// Options for formatting multiline strings.
|
|
pub const MultilineStringOptions = struct {
|
|
/// If top level is true, whitespace before and after the multiline string is elided.
|
|
/// If it is true, a newline is printed, then the value, followed by a newline, and if
|
|
/// whitespace is true any necessary indentation follows.
|
|
top_level: bool = false,
|
|
};
|
|
|
|
/// Like `value`, but always serializes to a multiline string literal.
|
|
///
|
|
/// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
|
|
/// since multiline strings cannot represent CR without a following newline.
|
|
pub fn multilineString(
|
|
self: *Self,
|
|
val: []const u8,
|
|
options: MultilineStringOptions,
|
|
) (Writer.Error || error{InnerCarriageReturn})!void {
|
|
// Make sure the string does not contain any carriage returns not followed by a newline
|
|
var i: usize = 0;
|
|
while (i < val.len) : (i += 1) {
|
|
if (val[i] == '\r') {
|
|
if (i + 1 < val.len) {
|
|
if (val[i + 1] == '\n') {
|
|
i += 1;
|
|
continue;
|
|
}
|
|
}
|
|
return error.InnerCarriageReturn;
|
|
}
|
|
}
|
|
|
|
if (!options.top_level) {
|
|
try self.newline();
|
|
try self.indent();
|
|
}
|
|
|
|
try self.writer.writeAll("\\\\");
|
|
for (val) |c| {
|
|
if (c != '\r') {
|
|
try self.writer.writeByte(c); // We write newlines here even if whitespace off
|
|
if (c == '\n') {
|
|
try self.indent();
|
|
try self.writer.writeAll("\\\\");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!options.top_level) {
|
|
try self.writer.writeByte('\n'); // Even if whitespace off
|
|
try self.indent();
|
|
}
|
|
}
|
|
|
|
/// Create a `Struct` for writing ZON structs field by field.
|
|
pub fn beginStruct(
|
|
self: *Self,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Struct {
|
|
return Struct.begin(self, options);
|
|
}
|
|
|
|
/// Creates a `Tuple` for writing ZON tuples field by field.
|
|
pub fn beginTuple(
|
|
self: *Self,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Tuple {
|
|
return Tuple.begin(self, options);
|
|
}
|
|
|
|
fn indent(self: *Self) Writer.Error!void {
|
|
if (self.options.whitespace) {
|
|
try self.writer.writeByteNTimes(' ', 4 * self.indent_level);
|
|
}
|
|
}
|
|
|
|
fn newline(self: *Self) Writer.Error!void {
|
|
if (self.options.whitespace) {
|
|
try self.writer.writeByte('\n');
|
|
}
|
|
}
|
|
|
|
fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void {
|
|
if (self.containerShouldWrap(len)) {
|
|
try self.newline();
|
|
} else {
|
|
try self.space();
|
|
}
|
|
}
|
|
|
|
fn space(self: *Self) Writer.Error!void {
|
|
if (self.options.whitespace) {
|
|
try self.writer.writeByte(' ');
|
|
}
|
|
}
|
|
|
|
/// Writes ZON tuples field by field.
|
|
pub const Tuple = struct {
|
|
container: Container,
|
|
|
|
fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple {
|
|
return .{
|
|
.container = try Container.begin(parent, .anon, options),
|
|
};
|
|
}
|
|
|
|
/// Finishes serializing the tuple.
|
|
///
|
|
/// Prints a trailing comma as configured when appropriate, and the closing bracket.
|
|
pub fn end(self: *Tuple) Writer.Error!void {
|
|
try self.container.end();
|
|
self.* = undefined;
|
|
}
|
|
|
|
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
|
|
pub fn field(
|
|
self: *Tuple,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
try self.container.field(null, val, options);
|
|
}
|
|
|
|
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
|
|
pub fn fieldMaxDepth(
|
|
self: *Tuple,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
depth: usize,
|
|
) (Writer.Error || error{ExceededMaxDepth})!void {
|
|
try self.container.fieldMaxDepth(null, val, options, depth);
|
|
}
|
|
|
|
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by
|
|
/// `valueArbitraryDepth`.
|
|
pub fn fieldArbitraryDepth(
|
|
self: *Tuple,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
try self.container.fieldArbitraryDepth(null, val, options);
|
|
}
|
|
|
|
/// Starts a field with a struct as a value. Returns the struct.
|
|
pub fn beginStructField(
|
|
self: *Tuple,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Struct {
|
|
try self.fieldPrefix();
|
|
return self.container.serializer.beginStruct(options);
|
|
}
|
|
|
|
/// Starts a field with a tuple as a value. Returns the tuple.
|
|
pub fn beginTupleField(
|
|
self: *Tuple,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Tuple {
|
|
try self.fieldPrefix();
|
|
return self.container.serializer.beginTuple(options);
|
|
}
|
|
|
|
/// Print a field prefix. This prints any necessary commas, and whitespace as
|
|
/// configured. Useful if you want to serialize the field value yourself.
|
|
pub fn fieldPrefix(self: *Tuple) Writer.Error!void {
|
|
try self.container.fieldPrefix(null);
|
|
}
|
|
};
|
|
|
|
/// Writes ZON structs field by field.
|
|
pub const Struct = struct {
|
|
container: Container,
|
|
|
|
fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct {
|
|
return .{
|
|
.container = try Container.begin(parent, .named, options),
|
|
};
|
|
}
|
|
|
|
/// Finishes serializing the struct.
|
|
///
|
|
/// Prints a trailing comma as configured when appropriate, and the closing bracket.
|
|
pub fn end(self: *Struct) Writer.Error!void {
|
|
try self.container.end();
|
|
self.* = undefined;
|
|
}
|
|
|
|
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
|
|
pub fn field(
|
|
self: *Struct,
|
|
name: []const u8,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
try self.container.field(name, val, options);
|
|
}
|
|
|
|
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
|
|
pub fn fieldMaxDepth(
|
|
self: *Struct,
|
|
name: []const u8,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
depth: usize,
|
|
) (Writer.Error || error{ExceededMaxDepth})!void {
|
|
try self.container.fieldMaxDepth(name, val, options, depth);
|
|
}
|
|
|
|
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by
|
|
/// `valueArbitraryDepth`.
|
|
pub fn fieldArbitraryDepth(
|
|
self: *Struct,
|
|
name: []const u8,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
try self.container.fieldArbitraryDepth(name, val, options);
|
|
}
|
|
|
|
/// Starts a field with a struct as a value. Returns the struct.
|
|
pub fn beginStructField(
|
|
self: *Struct,
|
|
name: []const u8,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Struct {
|
|
try self.fieldPrefix(name);
|
|
return self.container.serializer.beginStruct(options);
|
|
}
|
|
|
|
/// Starts a field with a tuple as a value. Returns the tuple.
|
|
pub fn beginTupleField(
|
|
self: *Struct,
|
|
name: []const u8,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Tuple {
|
|
try self.fieldPrefix(name);
|
|
return self.container.serializer.beginTuple(options);
|
|
}
|
|
|
|
/// Print a field prefix. This prints any necessary commas, the field name (escaped if
|
|
/// necessary) and whitespace as configured. Useful if you want to serialize the field
|
|
/// value yourself.
|
|
pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void {
|
|
try self.container.fieldPrefix(name);
|
|
}
|
|
};
|
|
|
|
const Container = struct {
|
|
const FieldStyle = enum { named, anon };
|
|
|
|
serializer: *Self,
|
|
field_style: FieldStyle,
|
|
options: SerializeContainerOptions,
|
|
empty: bool,
|
|
|
|
fn begin(
|
|
sz: *Self,
|
|
field_style: FieldStyle,
|
|
options: SerializeContainerOptions,
|
|
) Writer.Error!Container {
|
|
if (options.shouldWrap()) sz.indent_level +|= 1;
|
|
try sz.writer.writeAll(".{");
|
|
return .{
|
|
.serializer = sz,
|
|
.field_style = field_style,
|
|
.options = options,
|
|
.empty = true,
|
|
};
|
|
}
|
|
|
|
fn end(self: *Container) Writer.Error!void {
|
|
if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
|
|
if (!self.empty) {
|
|
if (self.options.shouldWrap()) {
|
|
if (self.serializer.options.whitespace) {
|
|
try self.serializer.writer.writeByte(',');
|
|
}
|
|
try self.serializer.newline();
|
|
try self.serializer.indent();
|
|
} else if (!self.shouldElideSpaces()) {
|
|
try self.serializer.space();
|
|
}
|
|
}
|
|
try self.serializer.writer.writeByte('}');
|
|
self.* = undefined;
|
|
}
|
|
|
|
fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void {
|
|
if (!self.empty) {
|
|
try self.serializer.writer.writeByte(',');
|
|
}
|
|
self.empty = false;
|
|
if (self.options.shouldWrap()) {
|
|
try self.serializer.newline();
|
|
} else if (!self.shouldElideSpaces()) {
|
|
try self.serializer.space();
|
|
}
|
|
if (self.options.shouldWrap()) try self.serializer.indent();
|
|
if (name) |n| {
|
|
try self.serializer.ident(n);
|
|
try self.serializer.space();
|
|
try self.serializer.writer.writeByte('=');
|
|
try self.serializer.space();
|
|
}
|
|
}
|
|
|
|
fn field(
|
|
self: *Container,
|
|
name: ?[]const u8,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
comptime assert(!typeIsRecursive(@TypeOf(val)));
|
|
try self.fieldArbitraryDepth(name, val, options);
|
|
}
|
|
|
|
fn fieldMaxDepth(
|
|
self: *Container,
|
|
name: ?[]const u8,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
depth: usize,
|
|
) (Writer.Error || error{ExceededMaxDepth})!void {
|
|
try checkValueDepth(val, depth);
|
|
try self.fieldArbitraryDepth(name, val, options);
|
|
}
|
|
|
|
fn fieldArbitraryDepth(
|
|
self: *Container,
|
|
name: ?[]const u8,
|
|
val: anytype,
|
|
options: ValueOptions,
|
|
) Writer.Error!void {
|
|
try self.fieldPrefix(name);
|
|
try self.serializer.valueArbitraryDepth(val, options);
|
|
}
|
|
|
|
fn shouldElideSpaces(self: *const Container) bool {
|
|
return switch (self.options.whitespace_style) {
|
|
.fields => |fields| self.field_style != .named and fields == 1,
|
|
else => false,
|
|
};
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
/// Creates a new `Serializer` with the given writer and options.
|
|
pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeOf(writer)) {
|
|
return .init(writer, options);
|
|
}
|
|
|
|
fn expectSerializeEqual(
|
|
expected: []const u8,
|
|
value: anytype,
|
|
options: SerializeOptions,
|
|
) !void {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
try serialize(value, options, buf.writer());
|
|
try std.testing.expectEqualStrings(expected, buf.items);
|
|
}
|
|
|
|
test "std.zon stringify whitespace, high level API" {
|
|
try expectSerializeEqual(".{}", .{}, .{});
|
|
try expectSerializeEqual(".{}", .{}, .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{1}", .{1}, .{});
|
|
try expectSerializeEqual(".{1}", .{1}, .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{});
|
|
try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{});
|
|
try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{ .x = 1 }", .{ .x = 1 }, .{});
|
|
try expectSerializeEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{ 1, 2 }", .{ 1, 2 }, .{});
|
|
try expectSerializeEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{});
|
|
try expectSerializeEqual(".{1,2}", @as([2]u32, .{ 1, 2 }), .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{});
|
|
try expectSerializeEqual(".{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{});
|
|
try expectSerializeEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 2 }, .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\}
|
|
, .{ 1, 2, 3 }, .{});
|
|
try expectSerializeEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\}
|
|
, @as([3]u32, .{ 1, 2, 3 }), .{});
|
|
try expectSerializeEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false });
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\}
|
|
, @as([]const u32, &.{ 1, 2, 3 }), .{});
|
|
try expectSerializeEqual(
|
|
".{1,2,3}",
|
|
@as([]const u32, &.{ 1, 2, 3 }),
|
|
.{ .whitespace = false },
|
|
);
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .x = 1,
|
|
\\ .y = 2,
|
|
\\ .z = 3,
|
|
\\}
|
|
, .{ .x = 1, .y = 2, .z = 3 }, .{});
|
|
try expectSerializeEqual(
|
|
".{.x=1,.y=2,.z=3}",
|
|
.{ .x = 1, .y = 2, .z = 3 },
|
|
.{ .whitespace = false },
|
|
);
|
|
|
|
const Union = union(enum) { a: bool, b: i32, c: u8 };
|
|
|
|
try expectSerializeEqual(".{ .b = 1 }", Union{ .b = 1 }, .{});
|
|
try expectSerializeEqual(".{.b=1}", Union{ .b = 1 }, .{ .whitespace = false });
|
|
|
|
// Nested indentation where outer object doesn't wrap
|
|
try expectSerializeEqual(
|
|
\\.{ .inner = .{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\} }
|
|
, .{ .inner = .{ 1, 2, 3 } }, .{});
|
|
|
|
const UnionWithVoid = union(enum) { a, b: void, c: u8 };
|
|
|
|
try expectSerializeEqual(
|
|
\\.a
|
|
, UnionWithVoid.a, .{});
|
|
}
|
|
|
|
test "std.zon stringify whitespace, low level API" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
inline for (.{ true, false }) |whitespace| {
|
|
sz.options = .{ .whitespace = whitespace };
|
|
|
|
// Empty containers
|
|
{
|
|
var container = try sz.beginStruct(.{});
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{});
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 0 } });
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 0 } });
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Size 1
|
|
{
|
|
var container = try sz.beginStruct(.{});
|
|
try container.field("a", 1, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ .a = 1,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{});
|
|
try container.field(1, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 1,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field("a", 1, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
// We get extra spaces here, since we didn't know up front that there would only be one
|
|
// field.
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field(1, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ 1 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
|
|
try container.field("a", 1, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 1 } });
|
|
try container.field(1, .{});
|
|
try container.end();
|
|
try std.testing.expectEqualStrings(".{1}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Size 2
|
|
{
|
|
var container = try sz.beginStruct(.{});
|
|
try container.field("a", 1, .{});
|
|
try container.field("b", 2, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ .a = 1,
|
|
\\ .b = 2,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{});
|
|
try container.field(1, .{});
|
|
try container.field(2, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1,2}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field("a", 1, .{});
|
|
try container.field("b", 2, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field(1, .{});
|
|
try container.field(2, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1,2}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 2 } });
|
|
try container.field("a", 1, .{});
|
|
try container.field("b", 2, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 2 } });
|
|
try container.field(1, .{});
|
|
try container.field(2, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1,2}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Size 3
|
|
{
|
|
var container = try sz.beginStruct(.{});
|
|
try container.field("a", 1, .{});
|
|
try container.field("b", 2, .{});
|
|
try container.field("c", 3, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ .a = 1,
|
|
\\ .b = 2,
|
|
\\ .c = 3,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{});
|
|
try container.field(1, .{});
|
|
try container.field(2, .{});
|
|
try container.field(3, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field("a", 1, .{});
|
|
try container.field("b", 2, .{});
|
|
try container.field("c", 3, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field(1, .{});
|
|
try container.field(2, .{});
|
|
try container.field(3, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .fields = 3 } });
|
|
try container.field("a", 1, .{});
|
|
try container.field("b", 2, .{});
|
|
try container.field("c", 3, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ .a = 1,
|
|
\\ .b = 2,
|
|
\\ .c = 3,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
var container = try sz.beginTuple(.{ .whitespace_style = .{ .fields = 3 } });
|
|
try container.field(1, .{});
|
|
try container.field(2, .{});
|
|
try container.field(3, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\}
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Nested objects where the outer container doesn't wrap but the inner containers do
|
|
{
|
|
var container = try sz.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
|
|
try container.field("first", .{ 1, 2, 3 }, .{});
|
|
try container.field("second", .{ 4, 5, 6 }, .{});
|
|
try container.end();
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings(
|
|
\\.{ .first = .{
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3,
|
|
\\}, .second = .{
|
|
\\ 4,
|
|
\\ 5,
|
|
\\ 6,
|
|
\\} }
|
|
, buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings(
|
|
".{.first=.{1,2,3},.second=.{4,5,6}}",
|
|
buf.items,
|
|
);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
}
|
|
}
|
|
|
|
test "std.zon stringify utf8 codepoints" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
// Printable ASCII
|
|
try sz.int('a');
|
|
try std.testing.expectEqualStrings("97", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.codePoint('a');
|
|
try std.testing.expectEqualStrings("'a'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('a', .{ .emit_codepoint_literals = .always });
|
|
try std.testing.expectEqualStrings("'a'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('a', .{ .emit_codepoint_literals = .printable_ascii });
|
|
try std.testing.expectEqualStrings("'a'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('a', .{ .emit_codepoint_literals = .never });
|
|
try std.testing.expectEqualStrings("97", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Short escaped codepoint
|
|
try sz.int('\n');
|
|
try std.testing.expectEqualStrings("10", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.codePoint('\n');
|
|
try std.testing.expectEqualStrings("'\\n'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('\n', .{ .emit_codepoint_literals = .always });
|
|
try std.testing.expectEqualStrings("'\\n'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('\n', .{ .emit_codepoint_literals = .printable_ascii });
|
|
try std.testing.expectEqualStrings("10", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('\n', .{ .emit_codepoint_literals = .never });
|
|
try std.testing.expectEqualStrings("10", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Large codepoint
|
|
try sz.int('âš¡');
|
|
try std.testing.expectEqualStrings("9889", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.codePoint('âš¡');
|
|
try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('âš¡', .{ .emit_codepoint_literals = .always });
|
|
try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('âš¡', .{ .emit_codepoint_literals = .printable_ascii });
|
|
try std.testing.expectEqualStrings("9889", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value('âš¡', .{ .emit_codepoint_literals = .never });
|
|
try std.testing.expectEqualStrings("9889", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Invalid codepoint
|
|
try std.testing.expectError(error.InvalidCodepoint, sz.codePoint(0x110000 + 1));
|
|
|
|
try sz.int(0x110000 + 1);
|
|
try std.testing.expectEqualStrings("1114113", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .always });
|
|
try std.testing.expectEqualStrings("1114113", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii });
|
|
try std.testing.expectEqualStrings("1114113", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .never });
|
|
try std.testing.expectEqualStrings("1114113", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Valid codepoint, not a codepoint type
|
|
try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always });
|
|
try std.testing.expectEqualStrings("97", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii });
|
|
try std.testing.expectEqualStrings("97", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never });
|
|
try std.testing.expectEqualStrings("97", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Make sure value options are passed to children
|
|
try sz.value(.{ .c = 'âš¡' }, .{ .emit_codepoint_literals = .always });
|
|
try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(.{ .c = 'âš¡' }, .{ .emit_codepoint_literals = .never });
|
|
try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
test "std.zon stringify strings" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
// Minimal case
|
|
try sz.string("abcâš¡\n");
|
|
try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.tuple("abcâš¡\n", .{});
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 97,
|
|
\\ 98,
|
|
\\ 99,
|
|
\\ 226,
|
|
\\ 154,
|
|
\\ 161,
|
|
\\ 10,
|
|
\\}
|
|
, buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value("abcâš¡\n", .{});
|
|
try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value("abcâš¡\n", .{ .emit_strings_as_containers = true });
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 97,
|
|
\\ 98,
|
|
\\ 99,
|
|
\\ 226,
|
|
\\ 154,
|
|
\\ 161,
|
|
\\ 10,
|
|
\\}
|
|
, buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Value options are inherited by children
|
|
try sz.value(.{ .str = "abc" }, .{});
|
|
try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true });
|
|
try std.testing.expectEqualStrings(
|
|
\\.{ .str = .{
|
|
\\ 97,
|
|
\\ 98,
|
|
\\ 99,
|
|
\\} }
|
|
, buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can
|
|
// round trip correctly.
|
|
try sz.value("abc".*, .{});
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ 97,
|
|
\\ 98,
|
|
\\ 99,
|
|
\\}
|
|
, buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
test "std.zon stringify multiline strings" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
inline for (.{ true, false }) |whitespace| {
|
|
sz.options.whitespace = whitespace;
|
|
|
|
{
|
|
try sz.multilineString("", .{ .top_level = true });
|
|
try std.testing.expectEqualStrings("\\\\", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try sz.multilineString("abcâš¡", .{ .top_level = true });
|
|
try std.testing.expectEqualStrings("\\\\abcâš¡", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try sz.multilineString("abcâš¡\ndef", .{ .top_level = true });
|
|
try std.testing.expectEqualStrings("\\\\abcâš¡\n\\\\def", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try sz.multilineString("abcâš¡\r\ndef", .{ .top_level = true });
|
|
try std.testing.expectEqualStrings("\\\\abcâš¡\n\\\\def", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try sz.multilineString("\nabcâš¡", .{ .top_level = true });
|
|
try std.testing.expectEqualStrings("\\\\\n\\\\abcâš¡", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try sz.multilineString("\r\nabcâš¡", .{ .top_level = true });
|
|
try std.testing.expectEqualStrings("\\\\\n\\\\abcâš¡", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try sz.multilineString("abc\ndef", .{});
|
|
if (whitespace) {
|
|
try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", buf.items);
|
|
} else {
|
|
try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", buf.items);
|
|
}
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
const str: []const u8 = &.{ 'a', '\r', 'c' };
|
|
try sz.string(str);
|
|
try std.testing.expectEqualStrings("\"a\\rc\"", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
{
|
|
try std.testing.expectError(
|
|
error.InnerCarriageReturn,
|
|
sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}),
|
|
);
|
|
try std.testing.expectError(
|
|
error.InnerCarriageReturn,
|
|
sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}),
|
|
);
|
|
try std.testing.expectError(
|
|
error.InnerCarriageReturn,
|
|
sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
}
|
|
}
|
|
|
|
test "std.zon stringify skip default fields" {
|
|
const Struct = struct {
|
|
x: i32 = 2,
|
|
y: i8,
|
|
z: u32 = 4,
|
|
inner1: struct { a: u8 = 'z', b: u8 = 'y', c: u8 } = .{
|
|
.a = '1',
|
|
.b = '2',
|
|
.c = '3',
|
|
},
|
|
inner2: struct { u8, u8, u8 } = .{
|
|
'a',
|
|
'b',
|
|
'c',
|
|
},
|
|
inner3: struct { u8, u8, u8 } = .{
|
|
'a',
|
|
'b',
|
|
'c',
|
|
},
|
|
};
|
|
|
|
// Not skipping if not set
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .x = 2,
|
|
\\ .y = 3,
|
|
\\ .z = 4,
|
|
\\ .inner1 = .{
|
|
\\ .a = '1',
|
|
\\ .b = '2',
|
|
\\ .c = '3',
|
|
\\ },
|
|
\\ .inner2 = .{
|
|
\\ 'a',
|
|
\\ 'b',
|
|
\\ 'c',
|
|
\\ },
|
|
\\ .inner3 = .{
|
|
\\ 'a',
|
|
\\ 'b',
|
|
\\ 'd',
|
|
\\ },
|
|
\\}
|
|
,
|
|
Struct{
|
|
.y = 3,
|
|
.z = 4,
|
|
.inner1 = .{
|
|
.a = '1',
|
|
.b = '2',
|
|
.c = '3',
|
|
},
|
|
.inner3 = .{
|
|
'a',
|
|
'b',
|
|
'd',
|
|
},
|
|
},
|
|
.{ .emit_codepoint_literals = .always },
|
|
);
|
|
|
|
// Top level defaults
|
|
try expectSerializeEqual(
|
|
\\.{ .y = 3, .inner3 = .{
|
|
\\ 'a',
|
|
\\ 'b',
|
|
\\ 'd',
|
|
\\} }
|
|
,
|
|
Struct{
|
|
.y = 3,
|
|
.z = 4,
|
|
.inner1 = .{
|
|
.a = '1',
|
|
.b = '2',
|
|
.c = '3',
|
|
},
|
|
.inner3 = .{
|
|
'a',
|
|
'b',
|
|
'd',
|
|
},
|
|
},
|
|
.{
|
|
.emit_default_optional_fields = false,
|
|
.emit_codepoint_literals = .always,
|
|
},
|
|
);
|
|
|
|
// Inner types having defaults, and defaults changing the number of fields affecting the
|
|
// formatting
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .y = 3,
|
|
\\ .inner1 = .{ .b = '2', .c = '3' },
|
|
\\ .inner3 = .{
|
|
\\ 'a',
|
|
\\ 'b',
|
|
\\ 'd',
|
|
\\ },
|
|
\\}
|
|
,
|
|
Struct{
|
|
.y = 3,
|
|
.z = 4,
|
|
.inner1 = .{
|
|
.a = 'z',
|
|
.b = '2',
|
|
.c = '3',
|
|
},
|
|
.inner3 = .{
|
|
'a',
|
|
'b',
|
|
'd',
|
|
},
|
|
},
|
|
.{
|
|
.emit_default_optional_fields = false,
|
|
.emit_codepoint_literals = .always,
|
|
},
|
|
);
|
|
|
|
const DefaultStrings = struct {
|
|
foo: []const u8 = "abc",
|
|
};
|
|
try expectSerializeEqual(
|
|
\\.{}
|
|
,
|
|
DefaultStrings{ .foo = "abc" },
|
|
.{ .emit_default_optional_fields = false },
|
|
);
|
|
try expectSerializeEqual(
|
|
\\.{ .foo = "abcd" }
|
|
,
|
|
DefaultStrings{ .foo = "abcd" },
|
|
.{ .emit_default_optional_fields = false },
|
|
);
|
|
}
|
|
|
|
test "std.zon depth limits" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
|
|
const Recurse = struct { r: []const @This() };
|
|
|
|
// Normal operation
|
|
try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16);
|
|
try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer());
|
|
try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Max depth failing on non recursive type
|
|
try std.testing.expectError(
|
|
error.ExceededMaxDepth,
|
|
serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Max depth passing on recursive type
|
|
{
|
|
const maybe_recurse = Recurse{ .r = &.{} };
|
|
try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2);
|
|
try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Unchecked passing on recursive type
|
|
{
|
|
const maybe_recurse = Recurse{ .r = &.{} };
|
|
try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer());
|
|
try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Max depth failing on recursive type due to depth
|
|
{
|
|
var maybe_recurse = Recurse{ .r = &.{} };
|
|
maybe_recurse.r = &.{.{ .r = &.{} }};
|
|
try std.testing.expectError(
|
|
error.ExceededMaxDepth,
|
|
serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Same but for a slice
|
|
{
|
|
var temp: [1]Recurse = .{.{ .r = &.{} }};
|
|
const maybe_recurse: []const Recurse = &temp;
|
|
|
|
try std.testing.expectError(
|
|
error.ExceededMaxDepth,
|
|
serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
try std.testing.expectError(
|
|
error.ExceededMaxDepth,
|
|
sz.tupleMaxDepth(maybe_recurse, .{}, 2),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.tupleArbitraryDepth(maybe_recurse, .{});
|
|
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// A slice succeeding
|
|
{
|
|
var temp: [1]Recurse = .{.{ .r = &.{} }};
|
|
const maybe_recurse: []const Recurse = &temp;
|
|
|
|
try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3);
|
|
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
try sz.tupleMaxDepth(maybe_recurse, .{}, 3);
|
|
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.tupleArbitraryDepth(maybe_recurse, .{});
|
|
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Max depth failing on recursive type due to recursion
|
|
{
|
|
var temp: [1]Recurse = .{.{ .r = &.{} }};
|
|
temp[0].r = &temp;
|
|
const maybe_recurse: []const Recurse = &temp;
|
|
|
|
try std.testing.expectError(
|
|
error.ExceededMaxDepth,
|
|
serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
var sz = serializer(buf.writer(), .{});
|
|
try std.testing.expectError(
|
|
error.ExceededMaxDepth,
|
|
sz.tupleMaxDepth(maybe_recurse, .{}, 128),
|
|
);
|
|
try std.testing.expectEqualStrings("", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Max depth on other parts of the lower level API
|
|
{
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
const maybe_recurse: []const Recurse = &.{};
|
|
|
|
try std.testing.expectError(error.ExceededMaxDepth, sz.valueMaxDepth(1, .{}, 0));
|
|
try sz.valueMaxDepth(2, .{}, 1);
|
|
try sz.value(3, .{});
|
|
try sz.valueArbitraryDepth(maybe_recurse, .{});
|
|
|
|
var s = try sz.beginStruct(.{});
|
|
try std.testing.expectError(error.ExceededMaxDepth, s.fieldMaxDepth("a", 1, .{}, 0));
|
|
try s.fieldMaxDepth("b", 4, .{}, 1);
|
|
try s.field("c", 5, .{});
|
|
try s.fieldArbitraryDepth("d", maybe_recurse, .{});
|
|
try s.end();
|
|
|
|
var t = try sz.beginTuple(.{});
|
|
try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0));
|
|
try t.fieldMaxDepth(6, .{}, 1);
|
|
try t.field(7, .{});
|
|
try t.fieldArbitraryDepth(maybe_recurse, .{});
|
|
try t.end();
|
|
|
|
var a = try sz.beginTuple(.{});
|
|
try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0));
|
|
try a.fieldMaxDepth(8, .{}, 1);
|
|
try a.field(9, .{});
|
|
try a.fieldArbitraryDepth(maybe_recurse, .{});
|
|
try a.end();
|
|
|
|
try std.testing.expectEqualStrings(
|
|
\\23.{}.{
|
|
\\ .b = 4,
|
|
\\ .c = 5,
|
|
\\ .d = .{},
|
|
\\}.{
|
|
\\ 6,
|
|
\\ 7,
|
|
\\ .{},
|
|
\\}.{
|
|
\\ 8,
|
|
\\ 9,
|
|
\\ .{},
|
|
\\}
|
|
, buf.items);
|
|
}
|
|
}
|
|
|
|
test "std.zon stringify primitives" {
|
|
// Issue: https://github.com/ziglang/zig/issues/20880
|
|
if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest;
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .a = 1.5,
|
|
\\ .b = 0.3333333333333333333333333333333333,
|
|
\\ .c = 3.1415926535897932384626433832795028,
|
|
\\ .d = 0,
|
|
\\ .e = -0,
|
|
\\ .f = inf,
|
|
\\ .g = -inf,
|
|
\\ .h = nan,
|
|
\\}
|
|
,
|
|
.{
|
|
.a = @as(f128, 1.5), // Make sure explicit f128s work
|
|
.b = 1.0 / 3.0,
|
|
.c = std.math.pi,
|
|
.d = 0.0,
|
|
.e = -0.0,
|
|
.f = std.math.inf(f32),
|
|
.g = -std.math.inf(f32),
|
|
.h = std.math.nan(f32),
|
|
},
|
|
.{},
|
|
);
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .a = 18446744073709551616,
|
|
\\ .b = -18446744073709551616,
|
|
\\ .c = 680564733841876926926749214863536422912,
|
|
\\ .d = -680564733841876926926749214863536422912,
|
|
\\ .e = 0,
|
|
\\}
|
|
,
|
|
.{
|
|
.a = 18446744073709551616,
|
|
.b = -18446744073709551616,
|
|
.c = 680564733841876926926749214863536422912,
|
|
.d = -680564733841876926926749214863536422912,
|
|
.e = 0,
|
|
},
|
|
.{},
|
|
);
|
|
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .a = true,
|
|
\\ .b = false,
|
|
\\ .c = .foo,
|
|
\\ .e = null,
|
|
\\}
|
|
,
|
|
.{
|
|
.a = true,
|
|
.b = false,
|
|
.c = .foo,
|
|
.e = null,
|
|
},
|
|
.{},
|
|
);
|
|
|
|
const Struct = struct { x: f32, y: f32 };
|
|
try expectSerializeEqual(
|
|
".{ .a = .{ .x = 1, .y = 2 }, .b = null }",
|
|
.{
|
|
.a = @as(?Struct, .{ .x = 1, .y = 2 }),
|
|
.b = @as(?Struct, null),
|
|
},
|
|
.{},
|
|
);
|
|
|
|
const E = enum(u8) {
|
|
foo,
|
|
bar,
|
|
};
|
|
try expectSerializeEqual(
|
|
".{ .a = .foo, .b = .foo }",
|
|
.{
|
|
.a = .foo,
|
|
.b = E.foo,
|
|
},
|
|
.{},
|
|
);
|
|
}
|
|
|
|
test "std.zon stringify ident" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{});
|
|
try sz.ident("a");
|
|
try std.testing.expectEqualStrings(".a", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("foo_1");
|
|
try std.testing.expectEqualStrings(".foo_1", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("_foo_1");
|
|
try std.testing.expectEqualStrings("._foo_1", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("foo bar");
|
|
try std.testing.expectEqualStrings(".@\"foo bar\"", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("1foo");
|
|
try std.testing.expectEqualStrings(".@\"1foo\"", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("var");
|
|
try std.testing.expectEqualStrings(".@\"var\"", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("true");
|
|
try std.testing.expectEqualStrings(".true", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
try sz.ident("_");
|
|
try std.testing.expectEqualStrings("._", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
const Enum = enum {
|
|
@"foo bar",
|
|
};
|
|
try expectSerializeEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{
|
|
.@"var" = .@"foo bar",
|
|
.@"1" = Enum.@"foo bar",
|
|
}, .{});
|
|
}
|
|
|
|
test "std.zon stringify as tuple" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
// Tuples
|
|
try sz.tuple(.{ 1, 2 }, .{});
|
|
try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Slice
|
|
try sz.tuple(@as([]const u8, &.{ 1, 2 }), .{});
|
|
try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Array
|
|
try sz.tuple([2]u8{ 1, 2 }, .{});
|
|
try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
test "std.zon stringify as float" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
// Comptime float
|
|
try sz.float(2.5);
|
|
try std.testing.expectEqualStrings("2.5", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
|
|
// Sized float
|
|
try sz.float(@as(f32, 2.5));
|
|
try std.testing.expectEqualStrings("2.5", buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
test "std.zon stringify vector" {
|
|
try expectSerializeEqual(
|
|
\\.{
|
|
\\ .{},
|
|
\\ .{
|
|
\\ true,
|
|
\\ false,
|
|
\\ true,
|
|
\\ },
|
|
\\ .{},
|
|
\\ .{
|
|
\\ 1.5,
|
|
\\ 2.5,
|
|
\\ 3.5,
|
|
\\ },
|
|
\\ .{},
|
|
\\ .{
|
|
\\ 2,
|
|
\\ 4,
|
|
\\ 6,
|
|
\\ },
|
|
\\ .{ 1, 2 },
|
|
\\ .{
|
|
\\ 3,
|
|
\\ 4,
|
|
\\ null,
|
|
\\ },
|
|
\\}
|
|
,
|
|
.{
|
|
@Vector(0, bool){},
|
|
@Vector(3, bool){ true, false, true },
|
|
@Vector(0, f32){},
|
|
@Vector(3, f32){ 1.5, 2.5, 3.5 },
|
|
@Vector(0, u8){},
|
|
@Vector(3, u8){ 2, 4, 6 },
|
|
@Vector(2, *const u8){ &1, &2 },
|
|
@Vector(3, ?*const u8){ &3, &4, null },
|
|
},
|
|
.{},
|
|
);
|
|
}
|
|
|
|
test "std.zon pointers" {
|
|
// Primitive with varying levels of pointers
|
|
try expectSerializeEqual("10", &@as(u32, 10), .{});
|
|
try expectSerializeEqual("10", &&@as(u32, 10), .{});
|
|
try expectSerializeEqual("10", &&&@as(u32, 10), .{});
|
|
|
|
// Primitive optional with varying levels of pointers
|
|
try expectSerializeEqual("10", @as(?*const u32, &10), .{});
|
|
try expectSerializeEqual("null", @as(?*const u32, null), .{});
|
|
try expectSerializeEqual("10", @as(?*const u32, &10), .{});
|
|
try expectSerializeEqual("null", @as(*const ?u32, &null), .{});
|
|
|
|
try expectSerializeEqual("10", @as(?*const *const u32, &&10), .{});
|
|
try expectSerializeEqual("null", @as(?*const *const u32, null), .{});
|
|
try expectSerializeEqual("10", @as(*const ?*const u32, &&10), .{});
|
|
try expectSerializeEqual("null", @as(*const ?*const u32, &null), .{});
|
|
try expectSerializeEqual("10", @as(*const *const ?u32, &&10), .{});
|
|
try expectSerializeEqual("null", @as(*const *const ?u32, &&null), .{});
|
|
|
|
try expectSerializeEqual(".{ 1, 2 }", &[2]u32{ 1, 2 }, .{});
|
|
|
|
// A complicated type with nested internal pointers and string allocations
|
|
{
|
|
const Inner = struct {
|
|
f1: *const ?*const []const u8,
|
|
f2: *const ?*const []const u8,
|
|
};
|
|
const Outer = struct {
|
|
f1: *const ?*const Inner,
|
|
f2: *const ?*const Inner,
|
|
};
|
|
const val: ?*const Outer = &.{
|
|
.f1 = &&.{
|
|
.f1 = &null,
|
|
.f2 = &&"foo",
|
|
},
|
|
.f2 = &null,
|
|
};
|
|
|
|
try expectSerializeEqual(
|
|
\\.{ .f1 = .{ .f1 = null, .f2 = "foo" }, .f2 = null }
|
|
, val, .{});
|
|
}
|
|
}
|
|
|
|
test "std.zon tuple/struct field" {
|
|
var buf = std.ArrayList(u8).init(std.testing.allocator);
|
|
defer buf.deinit();
|
|
var sz = serializer(buf.writer(), .{});
|
|
|
|
// Test on structs
|
|
{
|
|
var root = try sz.beginStruct(.{});
|
|
{
|
|
var tuple = try root.beginTupleField("foo", .{});
|
|
try tuple.field(0, .{});
|
|
try tuple.field(1, .{});
|
|
try tuple.end();
|
|
}
|
|
{
|
|
var strct = try root.beginStructField("bar", .{});
|
|
try strct.field("a", 0, .{});
|
|
try strct.field("b", 1, .{});
|
|
try strct.end();
|
|
}
|
|
try root.end();
|
|
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ .foo = .{
|
|
\\ 0,
|
|
\\ 1,
|
|
\\ },
|
|
\\ .bar = .{
|
|
\\ .a = 0,
|
|
\\ .b = 1,
|
|
\\ },
|
|
\\}
|
|
, buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
|
|
// Test on tuples
|
|
{
|
|
var root = try sz.beginTuple(.{});
|
|
{
|
|
var tuple = try root.beginTupleField(.{});
|
|
try tuple.field(0, .{});
|
|
try tuple.field(1, .{});
|
|
try tuple.end();
|
|
}
|
|
{
|
|
var strct = try root.beginStructField(.{});
|
|
try strct.field("a", 0, .{});
|
|
try strct.field("b", 1, .{});
|
|
try strct.end();
|
|
}
|
|
try root.end();
|
|
|
|
try std.testing.expectEqualStrings(
|
|
\\.{
|
|
\\ .{
|
|
\\ 0,
|
|
\\ 1,
|
|
\\ },
|
|
\\ .{
|
|
\\ .a = 0,
|
|
\\ .b = 1,
|
|
\\ },
|
|
\\}
|
|
, buf.items);
|
|
buf.clearRetainingCapacity();
|
|
}
|
|
}
|