struct stringify [src]
Alias for std.zon.stringify
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
Non-exhaustive enums
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.
Members
- serialize (Function)
- serializeArbitraryDepth (Function)
- serializeMaxDepth (Function)
- SerializeOptions (struct)
Source
//! 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
//! * Non-exhaustive enums
//! * 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;
const Writer = std.Io.Writer;
const Serializer = std.zon.Serializer;
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: Serializer.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: *Writer) Writer.Error!void {
var s: Serializer = .{
.writer = writer,
.options = .{ .whitespace = options.whitespace },
};
try s.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: *Writer,
depth: usize,
) Serializer.DepthError!void {
var s: Serializer = .{
.writer = writer,
.options = .{ .whitespace = options.whitespace },
};
try s.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: *Writer,
) Serializer.Error!void {
var s: Serializer = .{
.writer = writer,
.options = .{ .whitespace = options.whitespace },
};
try s.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 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,
}
}
fn expectSerializeEqual(
expected: []const u8,
value: anytype,
options: SerializeOptions,
) !void {
var aw: Writer.Allocating = .init(std.testing.allocator);
const bw = &aw.writer;
defer aw.deinit();
try serialize(value, options, bw);
try std.testing.expectEqualStrings(expected, aw.written());
}
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 aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
for ([2]bool{ true, false }) |whitespace| {
s.options = .{ .whitespace = whitespace };
// Empty containers
{
var container = try s.beginStruct(.{});
try container.end();
try std.testing.expectEqualStrings(".{}", aw.written());
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{});
try container.end();
try std.testing.expectEqualStrings(".{}", aw.written());
aw.clearRetainingCapacity();
}
{
var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
try container.end();
try std.testing.expectEqualStrings(".{}", aw.written());
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
try container.end();
try std.testing.expectEqualStrings(".{}", aw.written());
aw.clearRetainingCapacity();
}
{
var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 0 } });
try container.end();
try std.testing.expectEqualStrings(".{}", aw.written());
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 0 } });
try container.end();
try std.testing.expectEqualStrings(".{}", aw.written());
aw.clearRetainingCapacity();
}
// Size 1
{
var container = try s.beginStruct(.{});
try container.field("a", 1, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(
\\.{
\\ .a = 1,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{});
try container.field(1, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(
\\.{
\\ 1,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{1}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
try container.field("a", 1, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(".{ .a = 1 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1}", aw.written());
}
aw.clearRetainingCapacity();
}
{
// We get extra spaces here, since we didn't know up front that there would only be one
// field.
var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
try container.field(1, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(".{ 1 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{1}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
try container.field("a", 1, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(".{ .a = 1 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 1 } });
try container.field(1, .{});
try container.end();
try std.testing.expectEqualStrings(".{1}", aw.written());
aw.clearRetainingCapacity();
}
// Size 2
{
var container = try s.beginStruct(.{});
try container.field("a", 1, .{});
try container.field("b", 2, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(
\\.{
\\ .a = 1,
\\ .b = 2,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{});
try container.field(1, .{});
try container.field(2, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(
\\.{
\\ 1,
\\ 2,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{1,2}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
try container.field(1, .{});
try container.field(2, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{1,2}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 2 } });
try container.field(1, .{});
try container.field(2, .{});
try container.end();
if (whitespace) {
try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{1,2}", aw.written());
}
aw.clearRetainingCapacity();
}
// Size 3
{
var container = try s.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,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{1,2,3}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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 }", aw.written());
} else {
try std.testing.expectEqualStrings(".{1,2,3}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.written());
}
aw.clearRetainingCapacity();
}
{
var container = try s.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,
\\}
, aw.written());
} else {
try std.testing.expectEqualStrings(".{1,2,3}", aw.written());
}
aw.clearRetainingCapacity();
}
// Nested objects where the outer container doesn't wrap but the inner containers do
{
var container = try s.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,
\\} }
, aw.written());
} else {
try std.testing.expectEqualStrings(
".{.first=.{1,2,3},.second=.{4,5,6}}",
aw.written(),
);
}
aw.clearRetainingCapacity();
}
}
}
test "std.zon stringify utf8 codepoints" {
var aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
// Printable ASCII
try s.int('a');
try std.testing.expectEqualStrings("97", aw.written());
aw.clearRetainingCapacity();
try s.codePoint('a');
try std.testing.expectEqualStrings("'a'", aw.written());
aw.clearRetainingCapacity();
try s.value('a', .{ .emit_codepoint_literals = .always });
try std.testing.expectEqualStrings("'a'", aw.written());
aw.clearRetainingCapacity();
try s.value('a', .{ .emit_codepoint_literals = .printable_ascii });
try std.testing.expectEqualStrings("'a'", aw.written());
aw.clearRetainingCapacity();
try s.value('a', .{ .emit_codepoint_literals = .never });
try std.testing.expectEqualStrings("97", aw.written());
aw.clearRetainingCapacity();
// Short escaped codepoint
try s.int('\n');
try std.testing.expectEqualStrings("10", aw.written());
aw.clearRetainingCapacity();
try s.codePoint('\n');
try std.testing.expectEqualStrings("'\\n'", aw.written());
aw.clearRetainingCapacity();
try s.value('\n', .{ .emit_codepoint_literals = .always });
try std.testing.expectEqualStrings("'\\n'", aw.written());
aw.clearRetainingCapacity();
try s.value('\n', .{ .emit_codepoint_literals = .printable_ascii });
try std.testing.expectEqualStrings("10", aw.written());
aw.clearRetainingCapacity();
try s.value('\n', .{ .emit_codepoint_literals = .never });
try std.testing.expectEqualStrings("10", aw.written());
aw.clearRetainingCapacity();
// Large codepoint
try s.int('⚡');
try std.testing.expectEqualStrings("9889", aw.written());
aw.clearRetainingCapacity();
try s.codePoint('⚡');
try std.testing.expectEqualStrings("'\\u{26a1}'", aw.written());
aw.clearRetainingCapacity();
try s.value('⚡', .{ .emit_codepoint_literals = .always });
try std.testing.expectEqualStrings("'\\u{26a1}'", aw.written());
aw.clearRetainingCapacity();
try s.value('⚡', .{ .emit_codepoint_literals = .printable_ascii });
try std.testing.expectEqualStrings("9889", aw.written());
aw.clearRetainingCapacity();
try s.value('⚡', .{ .emit_codepoint_literals = .never });
try std.testing.expectEqualStrings("9889", aw.written());
aw.clearRetainingCapacity();
// Invalid codepoint
try s.codePoint(0x110000 + 1);
try std.testing.expectEqualStrings("'\\u{110001}'", aw.written());
aw.clearRetainingCapacity();
try s.int(0x110000 + 1);
try std.testing.expectEqualStrings("1114113", aw.written());
aw.clearRetainingCapacity();
try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .always });
try std.testing.expectEqualStrings("1114113", aw.written());
aw.clearRetainingCapacity();
try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii });
try std.testing.expectEqualStrings("1114113", aw.written());
aw.clearRetainingCapacity();
try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .never });
try std.testing.expectEqualStrings("1114113", aw.written());
aw.clearRetainingCapacity();
// Valid codepoint, not a codepoint type
try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always });
try std.testing.expectEqualStrings("97", aw.written());
aw.clearRetainingCapacity();
try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii });
try std.testing.expectEqualStrings("97", aw.written());
aw.clearRetainingCapacity();
try s.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never });
try std.testing.expectEqualStrings("97", aw.written());
aw.clearRetainingCapacity();
// Make sure value options are passed to children
try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always });
try std.testing.expectEqualStrings(".{ .c = '\\u{26a1}' }", aw.written());
aw.clearRetainingCapacity();
try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never });
try std.testing.expectEqualStrings(".{ .c = 9889 }", aw.written());
aw.clearRetainingCapacity();
}
test "std.zon stringify strings" {
var aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
// Minimal case
try s.string("abc⚡\n");
try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", aw.written());
aw.clearRetainingCapacity();
try s.tuple("abc⚡\n", .{});
try std.testing.expectEqualStrings(
\\.{
\\ 97,
\\ 98,
\\ 99,
\\ 226,
\\ 154,
\\ 161,
\\ 10,
\\}
, aw.written());
aw.clearRetainingCapacity();
try s.value("abc⚡\n", .{});
try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", aw.written());
aw.clearRetainingCapacity();
try s.value("abc⚡\n", .{ .emit_strings_as_containers = true });
try std.testing.expectEqualStrings(
\\.{
\\ 97,
\\ 98,
\\ 99,
\\ 226,
\\ 154,
\\ 161,
\\ 10,
\\}
, aw.written());
aw.clearRetainingCapacity();
// Value options are inherited by children
try s.value(.{ .str = "abc" }, .{});
try std.testing.expectEqualStrings(".{ .str = \"abc\" }", aw.written());
aw.clearRetainingCapacity();
try s.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true });
try std.testing.expectEqualStrings(
\\.{ .str = .{
\\ 97,
\\ 98,
\\ 99,
\\} }
, aw.written());
aw.clearRetainingCapacity();
// Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can
// round trip correctly.
try s.value("abc".*, .{});
try std.testing.expectEqualStrings(
\\.{
\\ 97,
\\ 98,
\\ 99,
\\}
, aw.written());
aw.clearRetainingCapacity();
}
test "std.zon stringify multiline strings" {
var aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
inline for (.{ true, false }) |whitespace| {
s.options.whitespace = whitespace;
{
try s.multilineString("", .{ .top_level = true });
try std.testing.expectEqualStrings("\\\\", aw.written());
aw.clearRetainingCapacity();
}
{
try s.multilineString("abc⚡", .{ .top_level = true });
try std.testing.expectEqualStrings("\\\\abc⚡", aw.written());
aw.clearRetainingCapacity();
}
{
try s.multilineString("abc⚡\ndef", .{ .top_level = true });
try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.written());
aw.clearRetainingCapacity();
}
{
try s.multilineString("abc⚡\r\ndef", .{ .top_level = true });
try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.written());
aw.clearRetainingCapacity();
}
{
try s.multilineString("\nabc⚡", .{ .top_level = true });
try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.written());
aw.clearRetainingCapacity();
}
{
try s.multilineString("\r\nabc⚡", .{ .top_level = true });
try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.written());
aw.clearRetainingCapacity();
}
{
try s.multilineString("abc\ndef", .{});
if (whitespace) {
try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", aw.written());
} else {
try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", aw.written());
}
aw.clearRetainingCapacity();
}
{
const str: []const u8 = &.{ 'a', '\r', 'c' };
try s.string(str);
try std.testing.expectEqualStrings("\"a\\rc\"", aw.written());
aw.clearRetainingCapacity();
}
{
try std.testing.expectError(
error.InnerCarriageReturn,
s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}),
);
try std.testing.expectError(
error.InnerCarriageReturn,
s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}),
);
try std.testing.expectError(
error.InnerCarriageReturn,
s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}),
);
try std.testing.expectEqualStrings("", aw.written());
aw.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 aw: Writer.Allocating = .init(std.testing.allocator);
const bw = &aw.writer;
defer aw.deinit();
const Recurse = struct { r: []const @This() };
// Normal operation
try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, bw, 16);
try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.written());
aw.clearRetainingCapacity();
try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, bw);
try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.written());
aw.clearRetainingCapacity();
// Max depth failing on non recursive type
try std.testing.expectError(
error.ExceededMaxDepth,
serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, bw, 3),
);
try std.testing.expectEqualStrings("", aw.written());
aw.clearRetainingCapacity();
// Max depth passing on recursive type
{
const maybe_recurse = Recurse{ .r = &.{} };
try serializeMaxDepth(maybe_recurse, .{}, bw, 2);
try std.testing.expectEqualStrings(".{ .r = .{} }", aw.written());
aw.clearRetainingCapacity();
}
// Unchecked passing on recursive type
{
const maybe_recurse = Recurse{ .r = &.{} };
try serializeArbitraryDepth(maybe_recurse, .{}, bw);
try std.testing.expectEqualStrings(".{ .r = .{} }", aw.written());
aw.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, .{}, bw, 2),
);
try std.testing.expectEqualStrings("", aw.written());
aw.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, .{}, bw, 2),
);
try std.testing.expectEqualStrings("", aw.written());
aw.clearRetainingCapacity();
var s: Serializer = .{ .writer = bw };
try std.testing.expectError(
error.ExceededMaxDepth,
s.tupleMaxDepth(maybe_recurse, .{}, 2),
);
try std.testing.expectEqualStrings("", aw.written());
aw.clearRetainingCapacity();
try s.tupleArbitraryDepth(maybe_recurse, .{});
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
aw.clearRetainingCapacity();
}
// A slice succeeding
{
var temp: [1]Recurse = .{.{ .r = &.{} }};
const maybe_recurse: []const Recurse = &temp;
try serializeMaxDepth(maybe_recurse, .{}, bw, 3);
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
aw.clearRetainingCapacity();
var s: Serializer = .{ .writer = bw };
try s.tupleMaxDepth(maybe_recurse, .{}, 3);
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
aw.clearRetainingCapacity();
try s.tupleArbitraryDepth(maybe_recurse, .{});
try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
aw.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, .{}, bw, 128),
);
try std.testing.expectEqualStrings("", aw.written());
aw.clearRetainingCapacity();
var s: Serializer = .{ .writer = bw };
try std.testing.expectError(
error.ExceededMaxDepth,
s.tupleMaxDepth(maybe_recurse, .{}, 128),
);
try std.testing.expectEqualStrings("", aw.written());
aw.clearRetainingCapacity();
}
// Max depth on other parts of the lower level API
{
var s: Serializer = .{ .writer = bw };
const maybe_recurse: []const Recurse = &.{};
try std.testing.expectError(error.ExceededMaxDepth, s.valueMaxDepth(1, .{}, 0));
try s.valueMaxDepth(2, .{}, 1);
try s.value(3, .{});
try s.valueArbitraryDepth(maybe_recurse, .{});
var wip_struct = try s.beginStruct(.{});
try std.testing.expectError(error.ExceededMaxDepth, wip_struct.fieldMaxDepth("a", 1, .{}, 0));
try wip_struct.fieldMaxDepth("b", 4, .{}, 1);
try wip_struct.field("c", 5, .{});
try wip_struct.fieldArbitraryDepth("d", maybe_recurse, .{});
try wip_struct.end();
var t = try s.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 s.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,
\\ .{},
\\}
, aw.written());
}
}
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 = -0.0,
\\ .g = inf,
\\ .h = -inf,
\\ .i = 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 = @as(f128, -0.0),
.g = std.math.inf(f32),
.h = -std.math.inf(f32),
.i = 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 aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{});
try s.ident("a");
try std.testing.expectEqualStrings(".a", aw.written());
aw.clearRetainingCapacity();
try s.ident("foo_1");
try std.testing.expectEqualStrings(".foo_1", aw.written());
aw.clearRetainingCapacity();
try s.ident("_foo_1");
try std.testing.expectEqualStrings("._foo_1", aw.written());
aw.clearRetainingCapacity();
try s.ident("foo bar");
try std.testing.expectEqualStrings(".@\"foo bar\"", aw.written());
aw.clearRetainingCapacity();
try s.ident("1foo");
try std.testing.expectEqualStrings(".@\"1foo\"", aw.written());
aw.clearRetainingCapacity();
try s.ident("var");
try std.testing.expectEqualStrings(".@\"var\"", aw.written());
aw.clearRetainingCapacity();
try s.ident("true");
try std.testing.expectEqualStrings(".true", aw.written());
aw.clearRetainingCapacity();
try s.ident("_");
try std.testing.expectEqualStrings("._", aw.written());
aw.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 aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
// Tuples
try s.tuple(.{ 1, 2 }, .{});
try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
aw.clearRetainingCapacity();
// Slice
try s.tuple(@as([]const u8, &.{ 1, 2 }), .{});
try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
aw.clearRetainingCapacity();
// Array
try s.tuple([2]u8{ 1, 2 }, .{});
try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
aw.clearRetainingCapacity();
}
test "std.zon stringify as float" {
var aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
// Comptime float
try s.float(2.5);
try std.testing.expectEqualStrings("2.5", aw.written());
aw.clearRetainingCapacity();
// Sized float
try s.float(@as(f32, 2.5));
try std.testing.expectEqualStrings("2.5", aw.written());
aw.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 aw: Writer.Allocating = .init(std.testing.allocator);
var s: Serializer = .{ .writer = &aw.writer };
defer aw.deinit();
// Test on structs
{
var root = try s.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,
\\ },
\\}
, aw.written());
aw.clearRetainingCapacity();
}
// Test on tuples
{
var root = try s.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,
\\ },
\\}
, aw.written());
aw.clearRetainingCapacity();
}
}