1//! Lower level control over serialization, you can create a new instance with `serializer`.
2//!
3//! Useful when you want control over which fields are serialized, how they're represented,
4//! or want to write a ZON object that does not exist in memory.
5//!
6//! You can serialize values with `value`. To serialize recursive types, the following are provided:
7//! * `valueMaxDepth`
8//! * `valueArbitraryDepth`
9//!
10//! You can also serialize values using specific notations:
11//! * `int`
12//! * `float`
13//! * `codePoint`
14//! * `tuple`
15//! * `tupleMaxDepth`
16//! * `tupleArbitraryDepth`
17//! * `string`
18//! * `multilineString`
19//!
20//! For manual serialization of containers, see:
21//! * `beginStruct`
22//! * `beginTuple`
23
24options: Options = .{},
25indent_level: u8 = 0,
26writer: *Writer,
27
28const Serializer = @This();
29const std = @import("std");
30const assert = std.debug.assert;
31const Writer = std.Io.Writer;
32
33pub const Error = Writer.Error;
34pub const DepthError = Error || error{ExceededMaxDepth};
35
36pub const Options = struct {
37 /// If false, only syntactically necessary whitespace is emitted.
38 whitespace: bool = true,
39};
40
41/// Options for manual serialization of container types.
42pub const ContainerOptions = struct {
43 /// The whitespace style that should be used for this container. Ignored if whitespace is off.
44 whitespace_style: union(enum) {
45 /// If true, wrap every field. If false do not.
46 wrap: bool,
47 /// Automatically decide whether to wrap or not based on the number of fields. Following
48 /// the standard rule of thumb, containers with more than two fields are wrapped.
49 fields: usize,
50 } = .{ .wrap = true },
51
52 fn shouldWrap(self: ContainerOptions) bool {
53 return switch (self.whitespace_style) {
54 .wrap => |wrap| wrap,
55 .fields => |fields| fields > 2,
56 };
57 }
58};
59
60/// Options for serialization of an individual value.
61///
62/// See `SerializeOptions` for more information on these options.
63pub const ValueOptions = struct {
64 emit_codepoint_literals: EmitCodepointLiterals = .never,
65 emit_strings_as_containers: bool = false,
66 emit_default_optional_fields: bool = true,
67 escape_non_ascii: bool = false,
68};
69
70/// Determines when to emit Unicode code point literals as opposed to integer literals.
71pub const EmitCodepointLiterals = enum {
72 /// Never emit Unicode code point literals.
73 never,
74 /// Emit Unicode code point literals for any `u8` in the printable ASCII range.
75 printable_ascii,
76 /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer
77 /// whose value is a valid non-surrogate code point.
78 always,
79
80 /// If the value should be emitted as a Unicode codepoint, return it as a u21.
81 fn emitAsCodepoint(self: @This(), val: anytype) ?u21 {
82 // Rule out incompatible integer types
83 switch (@typeInfo(@TypeOf(val))) {
84 .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) {
85 return null;
86 },
87 .comptime_int => {},
88 else => comptime unreachable,
89 }
90
91 // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted
92 // to a u21 if it should.
93 switch (self) {
94 .always => {
95 const c = std.math.cast(u21, val) orelse return null;
96 if (!std.unicode.utf8ValidCodepoint(c)) return null;
97 return c;
98 },
99 .printable_ascii => {
100 const c = std.math.cast(u8, val) orelse return null;
101 if (!std.ascii.isPrint(c)) return null;
102 return c;
103 },
104 .never => {
105 return null;
106 },
107 }
108 }
109};
110
111/// Serialize a value, similar to `serialize`.
112pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
113 comptime assert(!typeIsRecursive(@TypeOf(val)));
114 return self.valueArbitraryDepth(val, options);
115}
116
117/// Serialize a value, similar to `serializeMaxDepth`.
118/// Can return `error.ExceededMaxDepth`.
119pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void {
120 try checkValueDepth(val, depth);
121 return self.valueArbitraryDepth(val, options);
122}
123
124/// Serialize a value, similar to `serializeArbitraryDepth`.
125pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
126 comptime assertCanSerializeType(@TypeOf(val));
127 switch (@typeInfo(@TypeOf(val))) {
128 .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
129 self.codePoint(c, .{ .escape_non_ascii = options.escape_non_ascii }) catch |err| switch (err) {
130 error.InvalidCodepoint => unreachable, // Already validated
131 else => |e| return e,
132 };
133 } else {
134 try self.int(val);
135 },
136 .float, .comptime_float => try self.float(val),
137 .bool, .null => try self.writer.print("{}", .{val}),
138 .enum_literal => try self.ident(@tagName(val)),
139 .@"enum" => try self.ident(@tagName(val)),
140 .pointer => |pointer| {
141 // Try to serialize as a string
142 const item: ?type = switch (@typeInfo(pointer.child)) {
143 .array => |array| array.child,
144 else => if (pointer.size == .slice) pointer.child else null,
145 };
146 if (item == u8 and
147 (pointer.sentinel() == null or pointer.sentinel() == 0) and
148 !options.emit_strings_as_containers)
149 {
150 return try self.string(val, .{ .escape_non_ascii = options.escape_non_ascii });
151 }
152
153 // Serialize as either a tuple or as the child type
154 switch (pointer.size) {
155 .slice => try self.tupleImpl(val, options),
156 .one => try self.valueArbitraryDepth(val.*, options),
157 else => comptime unreachable,
158 }
159 },
160 .array => {
161 try valueArbitraryDepthArray(self, @TypeOf(val), &val, options);
162 },
163 .vector => |vector| {
164 const array: [vector.len]vector.child = val;
165 try valueArbitraryDepthArray(self, @TypeOf(array), &array, options);
166 },
167 .@"struct" => |@"struct"| if (@"struct".is_tuple) {
168 var container = try self.beginTuple(
169 .{ .whitespace_style = .{ .fields = @"struct".field_names.len } },
170 );
171 inline for (val) |field_value| {
172 try container.fieldArbitraryDepth(field_value, options);
173 }
174 try container.end();
175 } else {
176 // Decide which fields to emit
177 const fields, const skipped: [@"struct".field_names.len]bool = if (options.emit_default_optional_fields) b: {
178 break :b .{ @"struct".field_names.len, @splat(false) };
179 } else b: {
180 var fields = @"struct".field_names.len;
181 var skipped: [@"struct".field_names.len]bool = @splat(false);
182 inline for (
183 @"struct".field_names,
184 @"struct".field_types,
185 @"struct".field_attrs,
186 &skipped,
187 ) |field_name, field_type, field_attrs, *skip| {
188 if (field_attrs.default_value_ptr) |ptr| {
189 const default: *const field_type = @ptrCast(@alignCast(ptr));
190 const field_value = @field(val, field_name);
191 if (std.meta.eql(field_value, default.*)) {
192 skip.* = true;
193 fields -= 1;
194 }
195 }
196 }
197 break :b .{ fields, skipped };
198 };
199
200 // Emit those fields
201 var container = try self.beginStruct(
202 .{ .whitespace_style = .{ .fields = fields } },
203 );
204 inline for (@"struct".field_names, skipped) |field_name, skip| {
205 if (!skip) {
206 try container.fieldArbitraryDepth(
207 field_name,
208 @field(val, field_name),
209 options,
210 );
211 }
212 }
213 try container.end();
214 },
215 .@"union" => |@"union"| {
216 comptime assert(@"union".tag_type != null);
217 switch (val) {
218 inline else => |pl, tag| if (@TypeOf(pl) == void)
219 try self.writer.print(".{s}", .{@tagName(tag)})
220 else {
221 var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
222
223 try container.fieldArbitraryDepth(
224 @tagName(tag),
225 pl,
226 options,
227 );
228
229 try container.end();
230 },
231 }
232 },
233 .optional => if (val) |inner| {
234 try self.valueArbitraryDepth(inner, options);
235 } else {
236 try self.writer.writeAll("null");
237 },
238
239 else => comptime unreachable,
240 }
241}
242
243fn valueArbitraryDepthArray(s: *Serializer, comptime A: type, array: *const A, options: ValueOptions) Error!void {
244 var container = try s.beginTuple(
245 .{ .whitespace_style = .{ .fields = array.len } },
246 );
247 for (array) |elem| {
248 try container.fieldArbitraryDepth(elem, options);
249 }
250 try container.end();
251}
252
253/// Serialize an integer.
254pub fn int(self: *Serializer, val: anytype) Error!void {
255 try self.writer.printInt(val, 10, .lower, .{});
256}
257
258/// Serialize a float.
259pub fn float(self: *Serializer, val: anytype) Error!void {
260 switch (@typeInfo(@TypeOf(val))) {
261 .float => if (std.math.isNan(val)) {
262 return self.writer.writeAll("nan");
263 } else if (std.math.isPositiveInf(val)) {
264 return self.writer.writeAll("inf");
265 } else if (std.math.isNegativeInf(val)) {
266 return self.writer.writeAll("-inf");
267 } else if (std.math.isNegativeZero(val)) {
268 return self.writer.writeAll("-0.0");
269 } else {
270 try self.writer.print("{d}", .{val});
271 },
272 .comptime_float => if (val == 0) {
273 return self.writer.writeAll("0");
274 } else {
275 try self.writer.print("{d}", .{val});
276 },
277 else => comptime unreachable,
278 }
279}
280
281/// Serialize `name` as an identifier prefixed with `.`.
282///
283/// Escapes the identifier if necessary.
284pub fn ident(self: *Serializer, name: []const u8) Error!void {
285 try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)});
286}
287
288pub const CodePointError = Error || error{InvalidCodepoint};
289/// Options for formatting code points.
290pub const CodePointOptions = struct {
291 escape_non_ascii: bool = false,
292};
293
294/// Serialize `val` as a Unicode codepoint.
295///
296/// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
297pub fn codePoint(
298 self: *Serializer,
299 val: u21,
300 options: CodePointOptions,
301) CodePointError!void {
302 try self.writer.writeByte('\'');
303 try self.writeCodepoint(val, .{
304 .escape_non_ascii = options.escape_non_ascii,
305 .quote_style = .single,
306 });
307 try self.writer.writeByte('\'');
308}
309
310/// Like `value`, but always serializes `val` as a tuple.
311///
312/// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
313pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
314 comptime assert(!typeIsRecursive(@TypeOf(val)));
315 try self.tupleArbitraryDepth(val, options);
316}
317
318/// Like `tuple`, but recursive types are allowed.
319///
320/// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
321pub fn tupleMaxDepth(
322 self: *Serializer,
323 val: anytype,
324 options: ValueOptions,
325 depth: usize,
326) DepthError!void {
327 try checkValueDepth(val, depth);
328 try self.tupleArbitraryDepth(val, options);
329}
330
331/// Like `tuple`, but recursive types are allowed.
332///
333/// It is the caller's responsibility to ensure that `val` does not contain cycles.
334pub fn tupleArbitraryDepth(
335 self: *Serializer,
336 val: anytype,
337 options: ValueOptions,
338) Error!void {
339 try self.tupleImpl(val, options);
340}
341
342fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
343 comptime assertCanSerializeType(@TypeOf(val));
344 switch (@typeInfo(@TypeOf(val))) {
345 .@"struct" => {
346 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
347 inline for (val) |item_val| {
348 try container.fieldArbitraryDepth(item_val, options);
349 }
350 try container.end();
351 },
352 .pointer, .array => {
353 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
354 for (val) |item_val| {
355 try container.fieldArbitraryDepth(item_val, options);
356 }
357 try container.end();
358 },
359 else => comptime unreachable,
360 }
361}
362
363/// Options for writing a Unicode codepoint.
364const WriteCodepointOptions = struct {
365 escape_non_ascii: bool = false,
366 /// If single quote style then single quotes are escaped, otherwise double quotes are escaped.
367 quote_style: enum { single, double } = .single,
368};
369
370/// Write a Unicode codepoint to the writer using the given options.
371///
372/// Returns `error.InvalidCodepoint` if `codepoint` is not a valid Unicode codepoint.
373fn writeCodepoint(
374 self: *Serializer,
375 codepoint: u21,
376 options: WriteCodepointOptions,
377) CodePointError!void {
378 switch (codepoint) {
379 // Printable ASCII
380 ' ', '!', '#'...'&', '('...'[', ']'...'~' => try self.writer.writeByte(@intCast(codepoint)),
381 // Unprintable ASCII
382 0x00...0x08, 0x0B, 0x0C, 0x0E...0x1F, 0x7F => try self.writer.print("\\x{x:0>2}", .{codepoint}),
383 // ASCII with special escapes
384 '\n' => try self.writer.writeAll("\\n"),
385 '\r' => try self.writer.writeAll("\\r"),
386 '\t' => try self.writer.writeAll("\\t"),
387 '\\' => try self.writer.writeAll("\\\\"),
388 // Quotes need escaping if they conflict with the in-use quote character
389 '\'' => if (options.quote_style == .single) try self.writer.writeAll("\\'") else try self.writer.writeByte('\''),
390 '\"' => if (options.quote_style == .double) try self.writer.writeAll("\\\"") else try self.writer.writeByte('"'),
391
392 // Surrogates can only be written with an escape
393 0xD800...0xDFFF => try self.writer.print("\\u{{{x}}}", .{codepoint}),
394 // Other valid codepoints
395 0x80...0xD7FF, 0xE000...0x10FFFF => if (options.escape_non_ascii) {
396 try self.writer.print("\\u{{{x}}}", .{codepoint});
397 } else {
398 var buf: [7]u8 = undefined;
399 const len = std.unicode.utf8Encode(codepoint, &buf) catch unreachable;
400 try self.writer.writeAll(buf[0..len]);
401 },
402 // Invalid codepoints
403 0x110000...std.math.maxInt(u21) => return error.InvalidCodepoint,
404 }
405}
406
407pub const StringOptions = struct {
408 escape_non_ascii: bool = false,
409};
410
411/// Like `value`, but always serializes `val` as a string.
412pub fn string(self: *Serializer, val: []const u8, options: StringOptions) Writer.Error!void {
413 try self.writer.writeByte('"');
414 // Batch write sequences of "raw" bytes (printable ASCII or non-escaped non-ASCII) for performance.
415 // `val[start..i]` contains pending raw bytes to write.
416 var start: usize = 0;
417 var i: usize = 0;
418 while (i < val.len) {
419 const byte = val[i];
420 // Check if this byte can be written as-is
421 const is_raw = switch (byte) {
422 ' ', '!', '#'...'[', ']'...'~' => true,
423 0x80...0xFF => !options.escape_non_ascii,
424 else => false,
425 };
426 if (is_raw) {
427 i += 1;
428 continue;
429 }
430 // Flush pending raw bytes
431 try self.writer.writeAll(val[start..i]);
432 // Handle the special character
433 if (byte >= 0x80) {
434 // Decode UTF-8 sequence and write the codepoint
435 const ulen = std.unicode.utf8ByteSequenceLength(byte) catch unreachable;
436 const codepoint = std.unicode.utf8Decode(val[i..][0..ulen]) catch unreachable;
437 // InvalidCodepoint cannot occur from valid UTF-8
438 self.writeCodepoint(codepoint, .{
439 .escape_non_ascii = options.escape_non_ascii,
440 .quote_style = .double,
441 }) catch unreachable;
442 i += ulen;
443 } else {
444 // ASCII character that needs escaping
445 self.writeCodepoint(byte, .{
446 .escape_non_ascii = options.escape_non_ascii,
447 .quote_style = .double,
448 }) catch unreachable; // InvalidCodepoint cannot occur for valid ASCII values
449 i += 1;
450 }
451 start = i;
452 }
453
454 try self.writer.writeAll(val[start..]);
455 try self.writer.writeByte('"');
456}
457
458/// Options for formatting multiline strings.
459pub const MultilineStringOptions = struct {
460 /// If top level is true, whitespace before and after the multiline string is elided.
461 /// If it is true, a newline is printed, then the value, followed by a newline, and if
462 /// whitespace is true any necessary indentation follows.
463 top_level: bool = false,
464};
465
466pub const MultilineStringError = Error || error{InnerCarriageReturn};
467
468/// Like `value`, but always serializes to a multiline string literal.
469///
470/// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
471/// since multiline strings cannot represent CR without a following newline.
472pub fn multilineString(
473 self: *Serializer,
474 val: []const u8,
475 options: MultilineStringOptions,
476) MultilineStringError!void {
477 // Make sure the string does not contain any carriage returns not followed by a newline
478 var i: usize = 0;
479 while (i < val.len) : (i += 1) {
480 if (val[i] == '\r') {
481 if (i + 1 < val.len) {
482 if (val[i + 1] == '\n') {
483 i += 1;
484 continue;
485 }
486 }
487 return error.InnerCarriageReturn;
488 }
489 }
490
491 if (!options.top_level) {
492 try self.newline();
493 try self.indent();
494 }
495
496 try self.writer.writeAll("\\\\");
497 for (val) |c| {
498 if (c != '\r') {
499 try self.writer.writeByte(c); // We write newlines here even if whitespace off
500 if (c == '\n') {
501 try self.indent();
502 try self.writer.writeAll("\\\\");
503 }
504 }
505 }
506
507 if (!options.top_level) {
508 try self.writer.writeByte('\n'); // Even if whitespace off
509 try self.indent();
510 }
511}
512
513/// Create a `Struct` for writing ZON structs field by field.
514pub fn beginStruct(self: *Serializer, options: ContainerOptions) Error!Struct {
515 return Struct.begin(self, options);
516}
517
518/// Creates a `Tuple` for writing ZON tuples field by field.
519pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple {
520 return Tuple.begin(self, options);
521}
522
523fn indent(self: *Serializer) Error!void {
524 if (self.options.whitespace) {
525 try self.writer.splatByteAll(' ', 4 * self.indent_level);
526 }
527}
528
529fn newline(self: *Serializer) Error!void {
530 if (self.options.whitespace) {
531 try self.writer.writeByte('\n');
532 }
533}
534
535fn newlineOrSpace(self: *Serializer, len: usize) Error!void {
536 if (self.containerShouldWrap(len)) {
537 try self.newline();
538 } else {
539 try self.space();
540 }
541}
542
543fn space(self: *Serializer) Error!void {
544 if (self.options.whitespace) {
545 try self.writer.writeByte(' ');
546 }
547}
548
549/// Writes ZON tuples field by field.
550pub const Tuple = struct {
551 container: Container,
552
553 fn begin(parent: *Serializer, options: ContainerOptions) Error!Tuple {
554 return .{
555 .container = try Container.begin(parent, .anon, options),
556 };
557 }
558
559 /// Finishes serializing the tuple.
560 ///
561 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
562 pub fn end(self: *Tuple) Error!void {
563 try self.container.end();
564 self.* = undefined;
565 }
566
567 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
568 pub fn field(
569 self: *Tuple,
570 val: anytype,
571 options: ValueOptions,
572 ) Error!void {
573 try self.container.field(null, val, options);
574 }
575
576 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
577 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
578 pub fn fieldMaxDepth(
579 self: *Tuple,
580 val: anytype,
581 options: ValueOptions,
582 depth: usize,
583 ) DepthError!void {
584 try self.container.fieldMaxDepth(null, val, options, depth);
585 }
586
587 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
588 /// `valueArbitraryDepth`.
589 pub fn fieldArbitraryDepth(
590 self: *Tuple,
591 val: anytype,
592 options: ValueOptions,
593 ) Error!void {
594 try self.container.fieldArbitraryDepth(null, val, options);
595 }
596
597 /// Starts a field with a struct as a value. Returns the struct.
598 pub fn beginStructField(
599 self: *Tuple,
600 options: ContainerOptions,
601 ) Error!Struct {
602 try self.fieldPrefix();
603 return self.container.serializer.beginStruct(options);
604 }
605
606 /// Starts a field with a tuple as a value. Returns the tuple.
607 pub fn beginTupleField(
608 self: *Tuple,
609 options: ContainerOptions,
610 ) Error!Tuple {
611 try self.fieldPrefix();
612 return self.container.serializer.beginTuple(options);
613 }
614
615 /// Print a field prefix. This prints any necessary commas, and whitespace as
616 /// configured. Useful if you want to serialize the field value yourself.
617 pub fn fieldPrefix(self: *Tuple) Error!void {
618 try self.container.fieldPrefix(null);
619 }
620};
621
622/// Writes ZON structs field by field.
623pub const Struct = struct {
624 container: Container,
625
626 fn begin(parent: *Serializer, options: ContainerOptions) Error!Struct {
627 return .{
628 .container = try Container.begin(parent, .named, options),
629 };
630 }
631
632 /// Finishes serializing the struct.
633 ///
634 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
635 pub fn end(self: *Struct) Error!void {
636 try self.container.end();
637 self.* = undefined;
638 }
639
640 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
641 pub fn field(
642 self: *Struct,
643 name: []const u8,
644 val: anytype,
645 options: ValueOptions,
646 ) Error!void {
647 try self.container.field(name, val, options);
648 }
649
650 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
651 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
652 pub fn fieldMaxDepth(
653 self: *Struct,
654 name: []const u8,
655 val: anytype,
656 options: ValueOptions,
657 depth: usize,
658 ) DepthError!void {
659 try self.container.fieldMaxDepth(name, val, options, depth);
660 }
661
662 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
663 /// `valueArbitraryDepth`.
664 pub fn fieldArbitraryDepth(
665 self: *Struct,
666 name: []const u8,
667 val: anytype,
668 options: ValueOptions,
669 ) Error!void {
670 try self.container.fieldArbitraryDepth(name, val, options);
671 }
672
673 /// Starts a field with a struct as a value. Returns the struct.
674 pub fn beginStructField(
675 self: *Struct,
676 name: []const u8,
677 options: ContainerOptions,
678 ) Error!Struct {
679 try self.fieldPrefix(name);
680 return self.container.serializer.beginStruct(options);
681 }
682
683 /// Starts a field with a tuple as a value. Returns the tuple.
684 pub fn beginTupleField(
685 self: *Struct,
686 name: []const u8,
687 options: ContainerOptions,
688 ) Error!Tuple {
689 try self.fieldPrefix(name);
690 return self.container.serializer.beginTuple(options);
691 }
692
693 /// Print a field prefix. This prints any necessary commas, the field name (escaped if
694 /// necessary) and whitespace as configured. Useful if you want to serialize the field
695 /// value yourself.
696 pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void {
697 try self.container.fieldPrefix(name);
698 }
699};
700
701const Container = struct {
702 const FieldStyle = enum { named, anon };
703
704 serializer: *Serializer,
705 field_style: FieldStyle,
706 options: ContainerOptions,
707 empty: bool,
708
709 fn begin(
710 sz: *Serializer,
711 field_style: FieldStyle,
712 options: ContainerOptions,
713 ) Error!Container {
714 if (options.shouldWrap()) sz.indent_level +|= 1;
715 try sz.writer.writeAll(".{");
716 return .{
717 .serializer = sz,
718 .field_style = field_style,
719 .options = options,
720 .empty = true,
721 };
722 }
723
724 fn end(self: *Container) Error!void {
725 if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
726 if (!self.empty) {
727 if (self.options.shouldWrap()) {
728 if (self.serializer.options.whitespace) {
729 try self.serializer.writer.writeByte(',');
730 }
731 try self.serializer.newline();
732 try self.serializer.indent();
733 } else if (!self.shouldElideSpaces()) {
734 try self.serializer.space();
735 }
736 }
737 try self.serializer.writer.writeByte('}');
738 self.* = undefined;
739 }
740
741 fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void {
742 if (!self.empty) {
743 try self.serializer.writer.writeByte(',');
744 }
745 self.empty = false;
746 if (self.options.shouldWrap()) {
747 try self.serializer.newline();
748 } else if (!self.shouldElideSpaces()) {
749 try self.serializer.space();
750 }
751 if (self.options.shouldWrap()) try self.serializer.indent();
752 if (name) |n| {
753 try self.serializer.ident(n);
754 try self.serializer.space();
755 try self.serializer.writer.writeByte('=');
756 try self.serializer.space();
757 }
758 }
759
760 fn field(
761 self: *Container,
762 name: ?[]const u8,
763 val: anytype,
764 options: ValueOptions,
765 ) Error!void {
766 comptime assert(!typeIsRecursive(@TypeOf(val)));
767 try self.fieldArbitraryDepth(name, val, options);
768 }
769
770 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
771 fn fieldMaxDepth(
772 self: *Container,
773 name: ?[]const u8,
774 val: anytype,
775 options: ValueOptions,
776 depth: usize,
777 ) DepthError!void {
778 try checkValueDepth(val, depth);
779 try self.fieldArbitraryDepth(name, val, options);
780 }
781
782 fn fieldArbitraryDepth(
783 self: *Container,
784 name: ?[]const u8,
785 val: anytype,
786 options: ValueOptions,
787 ) Error!void {
788 try self.fieldPrefix(name);
789 try self.serializer.valueArbitraryDepth(val, options);
790 }
791
792 fn shouldElideSpaces(self: *const Container) bool {
793 return switch (self.options.whitespace_style) {
794 .fields => |fields| self.field_style != .named and fields == 1,
795 else => false,
796 };
797 }
798};
799
800test Serializer {
801 var discarding: Writer.Discarding = .init(&.{});
802 var s: Serializer = .{ .writer = &discarding.writer };
803 var vec2 = try s.beginStruct(.{});
804 try vec2.field("x", 1.5, .{});
805 try vec2.fieldPrefix("prefix");
806 try s.value(2.5, .{});
807 try vec2.end();
808}
809
810inline fn typeIsRecursive(comptime T: type) bool {
811 return comptime typeIsRecursiveInner(T, &.{});
812}
813
814fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool {
815 for (prev_visited) |V| {
816 if (V == T) return true;
817 }
818 const visited = prev_visited ++ .{T};
819
820 return switch (@typeInfo(T)) {
821 .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited),
822 .optional => |optional| typeIsRecursiveInner(optional.child, visited),
823 .array => |array| typeIsRecursiveInner(array.child, visited),
824 .vector => |vector| typeIsRecursiveInner(vector.child, visited),
825 .@"struct" => |@"struct"| for (@"struct".field_types) |field_type| {
826 if (typeIsRecursiveInner(field_type, visited)) break true;
827 } else false,
828 .@"union" => |@"union"| inline for (@"union".field_types) |field_type| {
829 if (typeIsRecursiveInner(field_type, visited)) break true;
830 } else false,
831 else => false,
832 };
833}
834
835test typeIsRecursive {
836 try std.testing.expect(!typeIsRecursive(bool));
837 try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 }));
838 try std.testing.expect(!typeIsRecursive(struct { i32, i32 }));
839 try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() }));
840 try std.testing.expect(typeIsRecursive(struct {
841 a: struct {
842 const A = @This();
843 b: struct {
844 c: *struct {
845 a: ?A,
846 },
847 },
848 },
849 }));
850 try std.testing.expect(typeIsRecursive(struct {
851 a: [3]*@This(),
852 }));
853 try std.testing.expect(typeIsRecursive(struct {
854 a: union { a: i32, b: *@This() },
855 }));
856}
857
858fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void {
859 if (depth == 0) return error.ExceededMaxDepth;
860 const child_depth = depth - 1;
861
862 switch (@typeInfo(@TypeOf(val))) {
863 .pointer => |pointer| switch (pointer.size) {
864 .one => try checkValueDepth(val.*, child_depth),
865 .slice => for (val) |item| {
866 try checkValueDepth(item, child_depth);
867 },
868 .c, .many => {},
869 },
870 .array => for (val) |item| {
871 try checkValueDepth(item, child_depth);
872 },
873 .@"struct" => |@"struct"| inline for (@"struct".field_names) |field_name| {
874 try checkValueDepth(@field(val, field_name), child_depth);
875 },
876 .@"union" => |@"union"| if (@"union".tag_type == null) {
877 return;
878 } else switch (val) {
879 inline else => |payload| {
880 return checkValueDepth(payload, child_depth);
881 },
882 },
883 .optional => if (val) |inner| try checkValueDepth(inner, child_depth),
884 else => {},
885 }
886}
887
888fn expectValueDepthEquals(expected: usize, v: anytype) !void {
889 try checkValueDepth(v, expected);
890 try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1));
891}
892
893test checkValueDepth {
894 try expectValueDepthEquals(1, 10);
895 try expectValueDepthEquals(2, .{ .x = 1, .y = 2 });
896 try expectValueDepthEquals(2, .{ 1, 2 });
897 try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } });
898 try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 });
899 try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } });
900 try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 });
901 try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 });
902 try expectValueDepthEquals(2, @as(?u32, 1));
903 try expectValueDepthEquals(1, @as(?u32, null));
904 try expectValueDepthEquals(1, null);
905 try expectValueDepthEquals(3, &@as(?u32, 1));
906
907 // The pointer drops the implicit comptime-ness, so we need to specify 'comptime' here
908 try comptime expectValueDepthEquals(2, &1);
909
910 const Union = union(enum) {
911 x: u32,
912 y: struct { x: u32 },
913 };
914 try expectValueDepthEquals(2, Union{ .x = 1 });
915 try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } });
916
917 const Recurse = struct { r: ?*const @This() };
918 try expectValueDepthEquals(2, Recurse{ .r = null });
919 try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } });
920 try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } });
921
922 try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 }));
923 try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
924}
925
926inline fn assertCanSerializeType(T: type) void {
927 if (!canSerializeType(T)) @compileError("cannot serialize: " ++ @typeName(T));
928}
929
930inline fn canSerializeType(T: type) bool {
931 comptime return canSerializeTypeInner(T, &.{}, false);
932}
933
934fn canSerializeTypeInner(
935 T: type,
936 /// Visited structs and unions, to avoid infinite recursion.
937 /// Tracking more types is unnecessary, and a little complex due to optional nesting.
938 visited: []const type,
939 parent_is_optional: bool,
940) bool {
941 return switch (@typeInfo(T)) {
942 .bool,
943 .int,
944 .float,
945 .comptime_float,
946 .comptime_int,
947 .null,
948 .enum_literal,
949 => true,
950
951 .noreturn,
952 .void,
953 .type,
954 .undefined,
955 .error_union,
956 .error_set,
957 .@"fn",
958 .frame,
959 .@"anyframe",
960 .@"opaque",
961 .spirv,
962 => false,
963
964 .@"enum" => |@"enum"| @"enum".mode == .exhaustive,
965
966 .pointer => |pointer| switch (pointer.size) {
967 .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional),
968 .slice => canSerializeTypeInner(pointer.child, visited, false),
969 .many, .c => false,
970 },
971
972 .optional => |optional| if (parent_is_optional)
973 false
974 else
975 canSerializeTypeInner(optional.child, visited, true),
976
977 .array => |array| canSerializeTypeInner(array.child, visited, false),
978 .vector => |vector| canSerializeTypeInner(vector.child, visited, false),
979
980 .@"struct" => |@"struct"| {
981 for (visited) |V| if (T == V) return true;
982 const new_visited = visited ++ .{T};
983 for (@"struct".field_types) |field_type| {
984 if (!canSerializeTypeInner(field_type, new_visited, false)) return false;
985 }
986 return true;
987 },
988 .@"union" => |@"union"| {
989 for (visited) |V| if (T == V) return true;
990 const new_visited = visited ++ .{T};
991 if (@"union".tag_type == null) return false;
992 for (@"union".field_types) |field_type| {
993 if (field_type != void and !canSerializeTypeInner(field_type, new_visited, false)) {
994 return false;
995 }
996 }
997 return true;
998 },
999 };
1000}
1001
1002test canSerializeType {
1003 try std.testing.expect(!comptime canSerializeType(void));
1004 try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 }));
1005 try std.testing.expect(!comptime canSerializeType(struct { error{foo} }));
1006 try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 }));
1007 try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8)));
1008 try std.testing.expect(!comptime canSerializeType(*?[*c]u8));
1009 try std.testing.expect(!comptime canSerializeType(enum(u8) { _ }));
1010 try std.testing.expect(!comptime canSerializeType(union { foo: void }));
1011 try std.testing.expect(comptime canSerializeType(union(enum) { foo: void }));
1012 try std.testing.expect(comptime canSerializeType(comptime_float));
1013 try std.testing.expect(comptime canSerializeType(comptime_int));
1014 try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null }));
1015 try std.testing.expect(comptime canSerializeType(@TypeOf(.foo)));
1016 try std.testing.expect(comptime canSerializeType(?u8));
1017 try std.testing.expect(comptime canSerializeType(*?*u8));
1018 try std.testing.expect(comptime canSerializeType(?struct {
1019 foo: ?struct {
1020 ?union(enum) {
1021 a: ?@Vector(0, ?*u8),
1022 },
1023 ?struct {
1024 f: ?[]?u8,
1025 },
1026 },
1027 }));
1028 try std.testing.expect(!comptime canSerializeType(??u8));
1029 try std.testing.expect(!comptime canSerializeType(?*?u8));
1030 try std.testing.expect(!comptime canSerializeType(*?*?*u8));
1031 try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 }));
1032 try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 }));
1033 try std.testing.expect(comptime canSerializeType(struct { comptime_int }));
1034 try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo }));
1035 const Recursive = struct { foo: ?*@This() };
1036 try std.testing.expect(comptime canSerializeType(Recursive));
1037
1038 // Make sure we validate nested optional before we early out due to already having seen
1039 // a type recursion!
1040 try std.testing.expect(!comptime canSerializeType(struct {
1041 add_to_visited: ?u8,
1042 retrieve_from_visited: ??u8,
1043 }));
1044}