| 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 | |
| 24 | options: Options = .{}, |
| 25 | indent_level: u8 = 0, |
| 26 | writer: *Writer, |
| 27 | |
| 28 | const Serializer = @This(); |
| 29 | const std = @import("std"); |
| 30 | const assert = std.debug.assert; |
| 31 | const Writer = std.Io.Writer; |
| 32 | |
| 33 | pub const Error = Writer.Error; |
| 34 | pub const DepthError = Error || error{ExceededMaxDepth}; |
| 35 | |
| 36 | pub 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. |
| 42 | pub 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. |
| 63 | pub 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. |
| 71 | pub 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`. |
| 112 | pub 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`. |
| 119 | pub 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`. |
| 125 | pub 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 | |
| 243 | fn 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. |
| 254 | pub fn int(self: *Serializer, val: anytype) Error!void { |
| 255 | try self.writer.printInt(val, 10, .lower, .{}); |
| 256 | } |
| 257 | |
| 258 | /// Serialize a float. |
| 259 | pub 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. |
| 284 | pub fn ident(self: *Serializer, name: []const u8) Error!void { |
| 285 | try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)}); |
| 286 | } |
| 287 | |
| 288 | pub const CodePointError = Error || error{InvalidCodepoint}; |
| 289 | /// Options for formatting code points. |
| 290 | pub 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. |
| 297 | pub 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. |
| 313 | pub 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. |
| 321 | pub 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. |
| 334 | pub fn tupleArbitraryDepth( |
| 335 | self: *Serializer, |
| 336 | val: anytype, |
| 337 | options: ValueOptions, |
| 338 | ) Error!void { |
| 339 | try self.tupleImpl(val, options); |
| 340 | } |
| 341 | |
| 342 | fn 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. |
| 364 | const 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. |
| 373 | fn 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 | |
| 407 | pub const StringOptions = struct { |
| 408 | escape_non_ascii: bool = false, |
| 409 | }; |
| 410 | |
| 411 | /// Like `value`, but always serializes `val` as a string. |
| 412 | pub 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. |
| 459 | pub 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 | |
| 466 | pub 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. |
| 472 | pub 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. |
| 514 | pub 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. |
| 519 | pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple { |
| 520 | return Tuple.begin(self, options); |
| 521 | } |
| 522 | |
| 523 | fn indent(self: *Serializer) Error!void { |
| 524 | if (self.options.whitespace) { |
| 525 | try self.writer.splatByteAll(' ', 4 * self.indent_level); |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | fn newline(self: *Serializer) Error!void { |
| 530 | if (self.options.whitespace) { |
| 531 | try self.writer.writeByte('\n'); |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | fn 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 | |
| 543 | fn 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. |
| 550 | pub 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. |
| 623 | pub 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 | |
| 701 | const 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 | |
| 800 | test 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 | |
| 810 | inline fn typeIsRecursive(comptime T: type) bool { |
| 811 | return comptime typeIsRecursiveInner(T, &.{}); |
| 812 | } |
| 813 | |
| 814 | fn 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 | |
| 835 | test 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 | |
| 858 | fn 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 | |
| 888 | fn expectValueDepthEquals(expected: usize, v: anytype) !void { |
| 889 | try checkValueDepth(v, expected); |
| 890 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1)); |
| 891 | } |
| 892 | |
| 893 | test 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 | |
| 926 | inline fn assertCanSerializeType(T: type) void { |
| 927 | if (!canSerializeType(T)) @compileError("cannot serialize: " ++ @typeName(T)); |
| 928 | } |
| 929 | |
| 930 | inline fn canSerializeType(T: type) bool { |
| 931 | comptime return canSerializeTypeInner(T, &.{}, false); |
| 932 | } |
| 933 | |
| 934 | fn 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 | |
| 1002 | test 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 | } |