| ... | @@ -1233,42 +1233,119 @@ pub const Value = union(enum) { | ... | @@ -1233,42 +1233,119 @@ pub const Value = union(enum) { |
| 1233 | Array: Array, | 1233 | Array: Array, |
| 1234 | Object: ObjectMap, | 1234 | Object: ObjectMap, |
| 1235 | | 1235 | |
| | 1236 | pub fn jsonStringify( |
| | 1237 | value: @This(), |
| | 1238 | options: StringifyOptions, |
| | 1239 | out_stream: var, |
| | 1240 | ) @TypeOf(out_stream).Error!void { |
| | 1241 | switch (value) { |
| | 1242 | .Null => try stringify(null, options, out_stream), |
| | 1243 | .Bool => |inner| try stringify(inner, options, out_stream), |
| | 1244 | .Integer => |inner| try stringify(inner, options, out_stream), |
| | 1245 | .Float => |inner| try stringify(inner, options, out_stream), |
| | 1246 | .String => |inner| try stringify(inner, options, out_stream), |
| | 1247 | .Array => |inner| try stringify(inner.span(), options, out_stream), |
| | 1248 | .Object => |inner| { |
| | 1249 | try out_stream.writeByte('{'); |
| | 1250 | var field_output = false; |
| | 1251 | var child_options = options; |
| | 1252 | if (child_options.whitespace) |*child_whitespace| { |
| | 1253 | child_whitespace.indent_level += 1; |
| | 1254 | } |
| | 1255 | var it = inner.iterator(); |
| | 1256 | while (it.next()) |entry| { |
| | 1257 | if (!field_output) { |
| | 1258 | field_output = true; |
| | 1259 | } else { |
| | 1260 | try out_stream.writeByte(','); |
| | 1261 | } |
| | 1262 | if (child_options.whitespace) |child_whitespace| { |
| | 1263 | try out_stream.writeByte('\n'); |
| | 1264 | try child_whitespace.outputIndent(out_stream); |
| | 1265 | } |
| | 1266 | |
| | 1267 | try stringify(entry.key, options, out_stream); |
| | 1268 | try out_stream.writeByte(':'); |
| | 1269 | if (child_options.whitespace) |child_whitespace| { |
| | 1270 | if (child_whitespace.separator) { |
| | 1271 | try out_stream.writeByte(' '); |
| | 1272 | } |
| | 1273 | } |
| | 1274 | try stringify(entry.value, child_options, out_stream); |
| | 1275 | } |
| | 1276 | if (field_output) { |
| | 1277 | if (options.whitespace) |whitespace| { |
| | 1278 | try out_stream.writeByte('\n'); |
| | 1279 | try whitespace.outputIndent(out_stream); |
| | 1280 | } |
| | 1281 | } |
| | 1282 | try out_stream.writeByte('}'); |
| | 1283 | }, |
| | 1284 | } |
| | 1285 | } |
| | 1286 | |
| 1236 | pub fn dump(self: Value) void { | 1287 | pub fn dump(self: Value) void { |
| 1237 | var held = std.debug.getStderrMutex().acquire(); | 1288 | var held = std.debug.getStderrMutex().acquire(); |
| 1238 | defer held.release(); | 1289 | defer held.release(); |
| 1239 | | 1290 | |
| 1240 | const stderr = std.debug.getStderrStream(); | 1291 | const stderr = std.debug.getStderrStream(); |
| 1241 | self.dumpStream(stderr, 1024) catch return; | 1292 | std.json.stringify(self, std.json.StringifyOptions{ .whitespace = null }, stderr) catch return; |
| 1242 | } | 1293 | } |
| | 1294 | }; |
| 1243 | | 1295 | |
| 1244 | pub fn dumpIndent(self: Value, comptime indent: usize) void { | 1296 | test "Value.jsonStringify" { |
| 1245 | if (indent == 0) { | 1297 | { |
| 1246 | self.dump(); | 1298 | var buffer: [10]u8 = undefined; |
| 1247 | } else { | 1299 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1248 | var held = std.debug.getStderrMutex().acquire(); | 1300 | try @as(Value, .Null).jsonStringify(.{}, fbs.outStream()); |
| 1249 | defer held.release(); | 1301 | testing.expectEqualSlices(u8, fbs.getWritten(), "null"); |
| 1250 | | | |
| 1251 | const stderr = std.debug.getStderrStream(); | | |
| 1252 | self.dumpStreamIndent(indent, stderr, 1024) catch return; | | |
| 1253 | } | | |
| 1254 | } | 1302 | } |
| 1255 | | 1303 | { |
| 1256 | pub fn dumpStream(self: @This(), stream: var, comptime max_depth: usize) !void { | 1304 | var buffer: [10]u8 = undefined; |
| 1257 | var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream); | 1305 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1258 | w.newline = ""; | 1306 | try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream()); |
| 1259 | w.one_indent = ""; | 1307 | testing.expectEqualSlices(u8, fbs.getWritten(), "true"); |
| 1260 | w.space = ""; | | |
| 1261 | try w.emitJson(self); | | |
| 1262 | } | 1308 | } |
| 1263 | | 1309 | { |
| 1264 | pub fn dumpStreamIndent(self: @This(), comptime indent: usize, stream: var, comptime max_depth: usize) !void { | 1310 | var buffer: [10]u8 = undefined; |
| 1265 | var one_indent = " " ** indent; | 1311 | var fbs = std.io.fixedBufferStream(&buffer); |
| 1266 | | 1312 | try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream()); |
| 1267 | var w = std.json.WriteStream(@TypeOf(stream).Child, max_depth).init(stream); | 1313 | testing.expectEqualSlices(u8, fbs.getWritten(), "42"); |
| 1268 | w.one_indent = one_indent; | | |
| 1269 | try w.emitJson(self); | | |
| 1270 | } | 1314 | } |
| 1271 | }; | 1315 | { |
| | 1316 | var buffer: [10]u8 = undefined; |
| | 1317 | var fbs = std.io.fixedBufferStream(&buffer); |
| | 1318 | try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.outStream()); |
| | 1319 | testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01"); |
| | 1320 | } |
| | 1321 | { |
| | 1322 | var buffer: [10]u8 = undefined; |
| | 1323 | var fbs = std.io.fixedBufferStream(&buffer); |
| | 1324 | try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.outStream()); |
| | 1325 | testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\""); |
| | 1326 | } |
| | 1327 | { |
| | 1328 | var buffer: [10]u8 = undefined; |
| | 1329 | var fbs = std.io.fixedBufferStream(&buffer); |
| | 1330 | try (Value{ |
| | 1331 | .Array = Array.fromOwnedSlice(undefined, &[_]Value{ |
| | 1332 | .{ .Integer = 1 }, |
| | 1333 | .{ .Integer = 2 }, |
| | 1334 | .{ .Integer = 3 }, |
| | 1335 | }), |
| | 1336 | }).jsonStringify(.{}, fbs.outStream()); |
| | 1337 | testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]"); |
| | 1338 | } |
| | 1339 | { |
| | 1340 | var buffer: [10]u8 = undefined; |
| | 1341 | var fbs = std.io.fixedBufferStream(&buffer); |
| | 1342 | var obj = ObjectMap.init(testing.allocator); |
| | 1343 | defer obj.deinit(); |
| | 1344 | try obj.putNoClobber("a", .{ .String = "b" }); |
| | 1345 | try (Value{ .Object = obj }).jsonStringify(.{}, fbs.outStream()); |
| | 1346 | testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}"); |
| | 1347 | } |
| | 1348 | } |
| 1272 | | 1349 | |
| 1273 | pub const ParseOptions = struct { | 1350 | pub const ParseOptions = struct { |
| 1274 | allocator: ?*Allocator = null, | 1351 | allocator: ?*Allocator = null, |
| ... | @@ -2241,11 +2318,86 @@ test "string copy option" { | ... | @@ -2241,11 +2318,86 @@ test "string copy option" { |
| 2241 | } | 2318 | } |
| 2242 | | 2319 | |
| 2243 | pub const StringifyOptions = struct { | 2320 | pub const StringifyOptions = struct { |
| 2244 | // TODO: indentation options? | 2321 | pub const Whitespace = struct { |
| 2245 | // TODO: make escaping '/' in strings optional? | 2322 | /// How many indentation levels deep are we? |
| 2246 | // TODO: allow picking if []u8 is string or array? | 2323 | indent_level: usize = 0, |
| | 2324 | |
| | 2325 | pub const Indentation = union(enum) { |
| | 2326 | Space: u8, |
| | 2327 | Tab: void, |
| | 2328 | }; |
| | 2329 | |
| | 2330 | /// What character(s) should be used for indentation? |
| | 2331 | indent: Indentation = Indentation{ .Space = 4 }, |
| | 2332 | |
| | 2333 | fn outputIndent( |
| | 2334 | whitespace: @This(), |
| | 2335 | out_stream: var, |
| | 2336 | ) @TypeOf(out_stream).Error!void { |
| | 2337 | var char: u8 = undefined; |
| | 2338 | var n_chars: usize = undefined; |
| | 2339 | switch (whitespace.indent) { |
| | 2340 | .Space => |n_spaces| { |
| | 2341 | char = ' '; |
| | 2342 | n_chars = n_spaces; |
| | 2343 | }, |
| | 2344 | .Tab => { |
| | 2345 | char = '\t'; |
| | 2346 | n_chars = 1; |
| | 2347 | }, |
| | 2348 | } |
| | 2349 | n_chars *= whitespace.indent_level; |
| | 2350 | try out_stream.writeByteNTimes(char, n_chars); |
| | 2351 | } |
| | 2352 | |
| | 2353 | /// After a colon, should whitespace be inserted? |
| | 2354 | separator: bool = true, |
| | 2355 | }; |
| | 2356 | |
| | 2357 | /// Controls the whitespace emitted |
| | 2358 | whitespace: ?Whitespace = null, |
| | 2359 | |
| | 2360 | /// Should []u8 be serialised as a string? or an array? |
| | 2361 | pub const StringOptions = union(enum) { |
| | 2362 | Array, |
| | 2363 | |
| | 2364 | /// String output options |
| | 2365 | const StringOutputOptions = struct { |
| | 2366 | /// Should '/' be escaped in strings? |
| | 2367 | escape_solidus: bool = false, |
| | 2368 | |
| | 2369 | /// Should unicode characters be escaped in strings? |
| | 2370 | escape_unicode: bool = false, |
| | 2371 | }; |
| | 2372 | String: StringOutputOptions, |
| | 2373 | }; |
| | 2374 | |
| | 2375 | string: StringOptions = StringOptions{ .String = .{} }, |
| 2247 | }; | 2376 | }; |
| 2248 | | 2377 | |
| | 2378 | fn outputUnicodeEscape( |
| | 2379 | codepoint: u21, |
| | 2380 | out_stream: var, |
| | 2381 | ) !void { |
| | 2382 | if (codepoint <= 0xFFFF) { |
| | 2383 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| | 2384 | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| | 2385 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. |
| | 2386 | try out_stream.writeAll("\\u"); |
| | 2387 | try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| | 2388 | } else { |
| | 2389 | assert(codepoint <= 0x10FFFF); |
| | 2390 | // To escape an extended character that is not in the Basic Multilingual Plane, |
| | 2391 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. |
| | 2392 | const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800; |
| | 2393 | const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00; |
| | 2394 | try out_stream.writeAll("\\u"); |
| | 2395 | try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| | 2396 | try out_stream.writeAll("\\u"); |
| | 2397 | try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); |
| | 2398 | } |
| | 2399 | } |
| | 2400 | |
| 2249 | pub fn stringify( | 2401 | pub fn stringify( |
| 2250 | value: var, | 2402 | value: var, |
| 2251 | options: StringifyOptions, | 2403 | options: StringifyOptions, |
| ... | @@ -2262,11 +2414,14 @@ pub fn stringify( | ... | @@ -2262,11 +2414,14 @@ pub fn stringify( |
| 2262 | .Bool => { | 2414 | .Bool => { |
| 2263 | return out_stream.writeAll(if (value) "true" else "false"); | 2415 | return out_stream.writeAll(if (value) "true" else "false"); |
| 2264 | }, | 2416 | }, |
| | 2417 | .Null => { |
| | 2418 | return out_stream.writeAll("null"); |
| | 2419 | }, |
| 2265 | .Optional => { | 2420 | .Optional => { |
| 2266 | if (value) |payload| { | 2421 | if (value) |payload| { |
| 2267 | return try stringify(payload, options, out_stream); | 2422 | return try stringify(payload, options, out_stream); |
| 2268 | } else { | 2423 | } else { |
| 2269 | return out_stream.writeAll("null"); | 2424 | return try stringify(null, options, out_stream); |
| 2270 | } | 2425 | } |
| 2271 | }, | 2426 | }, |
| 2272 | .Enum => { | 2427 | .Enum => { |
| ... | @@ -2297,8 +2452,12 @@ pub fn stringify( | ... | @@ -2297,8 +2452,12 @@ pub fn stringify( |
| 2297 | return value.jsonStringify(options, out_stream); | 2452 | return value.jsonStringify(options, out_stream); |
| 2298 | } | 2453 | } |
| 2299 | | 2454 | |
| 2300 | try out_stream.writeAll("{"); | 2455 | try out_stream.writeByte('{'); |
| 2301 | comptime var field_output = false; | 2456 | comptime var field_output = false; |
| | 2457 | var child_options = options; |
| | 2458 | if (child_options.whitespace) |*child_whitespace| { |
| | 2459 | child_whitespace.indent_level += 1; |
| | 2460 | } |
| 2302 | inline for (S.fields) |Field, field_i| { | 2461 | inline for (S.fields) |Field, field_i| { |
| 2303 | // don't include void fields | 2462 | // don't include void fields |
| 2304 | if (Field.field_type == void) continue; | 2463 | if (Field.field_type == void) continue; |
| ... | @@ -2306,14 +2465,28 @@ pub fn stringify( | ... | @@ -2306,14 +2465,28 @@ pub fn stringify( |
| 2306 | if (!field_output) { | 2465 | if (!field_output) { |
| 2307 | field_output = true; | 2466 | field_output = true; |
| 2308 | } else { | 2467 | } else { |
| 2309 | try out_stream.writeAll(","); | 2468 | try out_stream.writeByte(','); |
| | 2469 | } |
| | 2470 | if (child_options.whitespace) |child_whitespace| { |
| | 2471 | try out_stream.writeByte('\n'); |
| | 2472 | try child_whitespace.outputIndent(out_stream); |
| 2310 | } | 2473 | } |
| 2311 | | | |
| 2312 | try stringify(Field.name, options, out_stream); | 2474 | try stringify(Field.name, options, out_stream); |
| 2313 | try out_stream.writeAll(":"); | 2475 | try out_stream.writeByte(':'); |
| 2314 | try stringify(@field(value, Field.name), options, out_stream); | 2476 | if (child_options.whitespace) |child_whitespace| { |
| | 2477 | if (child_whitespace.separator) { |
| | 2478 | try out_stream.writeByte(' '); |
| | 2479 | } |
| | 2480 | } |
| | 2481 | try stringify(@field(value, Field.name), child_options, out_stream); |
| | 2482 | } |
| | 2483 | if (field_output) { |
| | 2484 | if (options.whitespace) |whitespace| { |
| | 2485 | try out_stream.writeByte('\n'); |
| | 2486 | try whitespace.outputIndent(out_stream); |
| | 2487 | } |
| 2315 | } | 2488 | } |
| 2316 | try out_stream.writeAll("}"); | 2489 | try out_stream.writeByte('}'); |
| 2317 | return; | 2490 | return; |
| 2318 | }, | 2491 | }, |
| 2319 | .Pointer => |ptr_info| switch (ptr_info.size) { | 2492 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| ... | @@ -2329,17 +2502,26 @@ pub fn stringify( | ... | @@ -2329,17 +2502,26 @@ pub fn stringify( |
| 2329 | }, | 2502 | }, |
| 2330 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) | 2503 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) |
| 2331 | .Slice => { | 2504 | .Slice => { |
| 2332 | if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { | 2505 | if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) { |
| 2333 | try out_stream.writeAll("\""); | 2506 | try out_stream.writeByte('\"'); |
| 2334 | var i: usize = 0; | 2507 | var i: usize = 0; |
| 2335 | while (i < value.len) : (i += 1) { | 2508 | while (i < value.len) : (i += 1) { |
| 2336 | switch (value[i]) { | 2509 | switch (value[i]) { |
| 2337 | // normal ascii characters | 2510 | // normal ascii character |
| 2338 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]), | 2511 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c), |
| 2339 | // control characters with short escapes | 2512 | // only 2 characters that *must* be escaped |
| 2340 | '\\' => try out_stream.writeAll("\\\\"), | 2513 | '\\' => try out_stream.writeAll("\\\\"), |
| 2341 | '\"' => try out_stream.writeAll("\\\""), | 2514 | '\"' => try out_stream.writeAll("\\\""), |
| 2342 | '/' => try out_stream.writeAll("\\/"), | 2515 | // solidus is optional to escape |
| | 2516 | '/' => { |
| | 2517 | if (options.string.String.escape_solidus) { |
| | 2518 | try out_stream.writeAll("\\/"); |
| | 2519 | } else { |
| | 2520 | try out_stream.writeByte('\\'); |
| | 2521 | } |
| | 2522 | }, |
| | 2523 | // control characters with short escapes |
| | 2524 | // TODO: option to switch between unicode and 'short' forms? |
| 2343 | 0x8 => try out_stream.writeAll("\\b"), | 2525 | 0x8 => try out_stream.writeAll("\\b"), |
| 2344 | 0xC => try out_stream.writeAll("\\f"), | 2526 | 0xC => try out_stream.writeAll("\\f"), |
| 2345 | '\n' => try out_stream.writeAll("\\n"), | 2527 | '\n' => try out_stream.writeAll("\\n"), |
| ... | @@ -2347,39 +2529,43 @@ pub fn stringify( | ... | @@ -2347,39 +2529,43 @@ pub fn stringify( |
| 2347 | '\t' => try out_stream.writeAll("\\t"), | 2529 | '\t' => try out_stream.writeAll("\\t"), |
| 2348 | else => { | 2530 | else => { |
| 2349 | const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; | 2531 | const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; |
| 2350 | const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; | 2532 | // control characters (only things left with 1 byte length) should always be printed as unicode escapes |
| 2351 | if (codepoint <= 0xFFFF) { | 2533 | if (ulen == 1 or options.string.String.escape_unicode) { |
| 2352 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), | 2534 | const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; |
| 2353 | // then it may be represented as a six-character sequence: a reverse solidus, followed | 2535 | try outputUnicodeEscape(codepoint, out_stream); |
| 2354 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. | | |
| 2355 | try out_stream.writeAll("\\u"); | | |
| 2356 | try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); | | |
| 2357 | } else { | 2536 | } else { |
| 2358 | // To escape an extended character that is not in the Basic Multilingual Plane, | 2537 | try out_stream.writeAll(value[i .. i + ulen]); |
| 2359 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. | | |
| 2360 | const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800; | | |
| 2361 | const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00; | | |
| 2362 | try out_stream.writeAll("\\u"); | | |
| 2363 | try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); | | |
| 2364 | try out_stream.writeAll("\\u"); | | |
| 2365 | try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); | | |
| 2366 | } | 2538 | } |
| 2367 | i += ulen - 1; | 2539 | i += ulen - 1; |
| 2368 | }, | 2540 | }, |
| 2369 | } | 2541 | } |
| 2370 | } | 2542 | } |
| 2371 | try out_stream.writeAll("\""); | 2543 | try out_stream.writeByte('\"'); |
| 2372 | return; | 2544 | return; |
| 2373 | } | 2545 | } |
| 2374 | | 2546 | |
| 2375 | try out_stream.writeAll("["); | 2547 | try out_stream.writeByte('['); |
| | 2548 | var child_options = options; |
| | 2549 | if (child_options.whitespace) |*whitespace| { |
| | 2550 | whitespace.indent_level += 1; |
| | 2551 | } |
| 2376 | for (value) |x, i| { | 2552 | for (value) |x, i| { |
| 2377 | if (i != 0) { | 2553 | if (i != 0) { |
| 2378 | try out_stream.writeAll(","); | 2554 | try out_stream.writeByte(','); |
| | 2555 | } |
| | 2556 | if (child_options.whitespace) |child_whitespace| { |
| | 2557 | try out_stream.writeByte('\n'); |
| | 2558 | try child_whitespace.outputIndent(out_stream); |
| 2379 | } | 2559 | } |
| 2380 | try stringify(x, options, out_stream); | 2560 | try stringify(x, child_options, out_stream); |
| 2381 | } | 2561 | } |
| 2382 | try out_stream.writeAll("]"); | 2562 | if (value.len != 0) { |
| | 2563 | if (options.whitespace) |whitespace| { |
| | 2564 | try out_stream.writeByte('\n'); |
| | 2565 | try whitespace.outputIndent(out_stream); |
| | 2566 | } |
| | 2567 | } |
| | 2568 | try out_stream.writeByte(']'); |
| 2383 | return; | 2569 | return; |
| 2384 | }, | 2570 | }, |
| 2385 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | 2571 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), |
| ... | @@ -2390,7 +2576,7 @@ pub fn stringify( | ... | @@ -2390,7 +2576,7 @@ pub fn stringify( |
| 2390 | unreachable; | 2576 | unreachable; |
| 2391 | } | 2577 | } |
| 2392 | | 2578 | |
| 2393 | fn teststringify(expected: []const u8, value: var) !void { | 2579 | fn teststringify(expected: []const u8, value: var, options: StringifyOptions) !void { |
| 2394 | const ValidationOutStream = struct { | 2580 | const ValidationOutStream = struct { |
| 2395 | const Self = @This(); | 2581 | const Self = @This(); |
| 2396 | pub const OutStream = std.io.OutStream(*Self, Error, write); | 2582 | pub const OutStream = std.io.OutStream(*Self, Error, write); |
| ... | @@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void { | ... | @@ -2442,55 +2628,105 @@ fn teststringify(expected: []const u8, value: var) !void { |
| 2442 | }; | 2628 | }; |
| 2443 | | 2629 | |
| 2444 | var vos = ValidationOutStream.init(expected); | 2630 | var vos = ValidationOutStream.init(expected); |
| 2445 | try stringify(value, StringifyOptions{}, vos.outStream()); | 2631 | try stringify(value, options, vos.outStream()); |
| 2446 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; | 2632 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| 2447 | } | 2633 | } |
| 2448 | | 2634 | |
| 2449 | test "stringify basic types" { | 2635 | test "stringify basic types" { |
| 2450 | try teststringify("false", false); | 2636 | try teststringify("false", false, StringifyOptions{}); |
| 2451 | try teststringify("true", true); | 2637 | try teststringify("true", true, StringifyOptions{}); |
| 2452 | try teststringify("null", @as(?u8, null)); | 2638 | try teststringify("null", @as(?u8, null), StringifyOptions{}); |
| 2453 | try teststringify("null", @as(?*u32, null)); | 2639 | try teststringify("null", @as(?*u32, null), StringifyOptions{}); |
| 2454 | try teststringify("42", 42); | 2640 | try teststringify("42", 42, StringifyOptions{}); |
| 2455 | try teststringify("4.2e+01", 42.0); | 2641 | try teststringify("4.2e+01", 42.0, StringifyOptions{}); |
| 2456 | try teststringify("42", @as(u8, 42)); | 2642 | try teststringify("42", @as(u8, 42), StringifyOptions{}); |
| 2457 | try teststringify("42", @as(u128, 42)); | 2643 | try teststringify("42", @as(u128, 42), StringifyOptions{}); |
| 2458 | try teststringify("4.2e+01", @as(f32, 42)); | 2644 | try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{}); |
| 2459 | try teststringify("4.2e+01", @as(f64, 42)); | 2645 | try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{}); |
| 2460 | } | 2646 | } |
| 2461 | | 2647 | |
| 2462 | test "stringify string" { | 2648 | test "stringify string" { |
| 2463 | try teststringify("\"hello\"", "hello"); | 2649 | try teststringify("\"hello\"", "hello", StringifyOptions{}); |
| 2464 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r"); | 2650 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{}); |
| 2465 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}"); | 2651 | try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2466 | try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}"); | 2652 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{}); |
| 2467 | try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}"); | 2653 | try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2468 | try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}"); | 2654 | try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{}); |
| 2469 | try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}"); | 2655 | try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2470 | try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}"); | 2656 | try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{}); |
| 2471 | try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}"); | 2657 | try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2472 | try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}"); | 2658 | try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{}); |
| 2473 | try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}"); | 2659 | try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| | 2660 | try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{}); |
| | 2661 | try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| | 2662 | try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{}); |
| | 2663 | try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| | 2664 | try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{}); |
| | 2665 | try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| | 2666 | try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{}); |
| | 2667 | try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| | 2668 | try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{}); |
| | 2669 | try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } }); |
| 2474 | } | 2670 | } |
| 2475 | | 2671 | |
| 2476 | test "stringify tagged unions" { | 2672 | test "stringify tagged unions" { |
| 2477 | try teststringify("42", union(enum) { | 2673 | try teststringify("42", union(enum) { |
| 2478 | Foo: u32, | 2674 | Foo: u32, |
| 2479 | Bar: bool, | 2675 | Bar: bool, |
| 2480 | }{ .Foo = 42 }); | 2676 | }{ .Foo = 42 }, StringifyOptions{}); |
| 2481 | } | 2677 | } |
| 2482 | | 2678 | |
| 2483 | test "stringify struct" { | 2679 | test "stringify struct" { |
| 2484 | try teststringify("{\"foo\":42}", struct { | 2680 | try teststringify("{\"foo\":42}", struct { |
| 2485 | foo: u32, | 2681 | foo: u32, |
| 2486 | }{ .foo = 42 }); | 2682 | }{ .foo = 42 }, StringifyOptions{}); |
| | 2683 | } |
| | 2684 | |
| | 2685 | test "stringify struct with indentation" { |
| | 2686 | try teststringify( |
| | 2687 | \\{ |
| | 2688 | \\ "foo": 42, |
| | 2689 | \\ "bar": [ |
| | 2690 | \\ 1, |
| | 2691 | \\ 2, |
| | 2692 | \\ 3 |
| | 2693 | \\ ] |
| | 2694 | \\} |
| | 2695 | , |
| | 2696 | struct { |
| | 2697 | foo: u32, |
| | 2698 | bar: [3]u32, |
| | 2699 | }{ |
| | 2700 | .foo = 42, |
| | 2701 | .bar = .{ 1, 2, 3 }, |
| | 2702 | }, |
| | 2703 | StringifyOptions{ |
| | 2704 | .whitespace = .{}, |
| | 2705 | }, |
| | 2706 | ); |
| | 2707 | try teststringify( |
| | 2708 | "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", |
| | 2709 | struct { |
| | 2710 | foo: u32, |
| | 2711 | bar: [3]u32, |
| | 2712 | }{ |
| | 2713 | .foo = 42, |
| | 2714 | .bar = .{ 1, 2, 3 }, |
| | 2715 | }, |
| | 2716 | StringifyOptions{ |
| | 2717 | .whitespace = .{ |
| | 2718 | .indent = .Tab, |
| | 2719 | .separator = false, |
| | 2720 | }, |
| | 2721 | }, |
| | 2722 | ); |
| 2487 | } | 2723 | } |
| 2488 | | 2724 | |
| 2489 | test "stringify struct with void field" { | 2725 | test "stringify struct with void field" { |
| 2490 | try teststringify("{\"foo\":42}", struct { | 2726 | try teststringify("{\"foo\":42}", struct { |
| 2491 | foo: u32, | 2727 | foo: u32, |
| 2492 | bar: void = {}, | 2728 | bar: void = {}, |
| 2493 | }{ .foo = 42 }); | 2729 | }{ .foo = 42 }, StringifyOptions{}); |
| 2494 | } | 2730 | } |
| 2495 | | 2731 | |
| 2496 | test "stringify array of structs" { | 2732 | test "stringify array of structs" { |
| ... | @@ -2501,7 +2737,7 @@ test "stringify array of structs" { | ... | @@ -2501,7 +2737,7 @@ test "stringify array of structs" { |
| 2501 | MyStruct{ .foo = 42 }, | 2737 | MyStruct{ .foo = 42 }, |
| 2502 | MyStruct{ .foo = 100 }, | 2738 | MyStruct{ .foo = 100 }, |
| 2503 | MyStruct{ .foo = 1000 }, | 2739 | MyStruct{ .foo = 1000 }, |
| 2504 | }); | 2740 | }, StringifyOptions{}); |
| 2505 | } | 2741 | } |
| 2506 | | 2742 | |
| 2507 | test "stringify struct with custom stringifier" { | 2743 | test "stringify struct with custom stringifier" { |
| ... | @@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" { | ... | @@ -2515,7 +2751,7 @@ test "stringify struct with custom stringifier" { |
| 2515 | ) !void { | 2751 | ) !void { |
| 2516 | try out_stream.writeAll("[\"something special\","); | 2752 | try out_stream.writeAll("[\"something special\","); |
| 2517 | try stringify(42, options, out_stream); | 2753 | try stringify(42, options, out_stream); |
| 2518 | try out_stream.writeAll("]"); | 2754 | try out_stream.writeByte(']'); |
| 2519 | } | 2755 | } |
| 2520 | }{ .foo = 42 }); | 2756 | }{ .foo = 42 }, StringifyOptions{}); |
| 2521 | } | 2757 | } |