| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | // | 2 | // |
| 3 | // https://tools.ietf.org/html/rfc8259 | 3 | // https://tools.ietf.org/html/rfc8259 |
| 4 | | 4 | |
| | 5 | const builtin = @import("builtin"); |
| 5 | const std = @import("std.zig"); | 6 | const std = @import("std.zig"); |
| 6 | const debug = std.debug; | 7 | const debug = std.debug; |
| 7 | const assert = debug.assert; | 8 | const assert = debug.assert; |
| ... | @@ -72,22 +73,6 @@ fn encodesTo(decoded: []const u8, encoded: []const u8) bool { | ... | @@ -72,22 +73,6 @@ fn encodesTo(decoded: []const u8, encoded: []const u8) bool { |
| 72 | return true; | 73 | return true; |
| 73 | } | 74 | } |
| 74 | | 75 | |
| 75 | test "encodesTo" { | | |
| 76 | // same | | |
| 77 | try testing.expectEqual(true, encodesTo("false", "false")); | | |
| 78 | // totally different | | |
| 79 | try testing.expectEqual(false, encodesTo("false", "true")); | | |
| 80 | // different lengths | | |
| 81 | try testing.expectEqual(false, encodesTo("false", "other")); | | |
| 82 | // with escape | | |
| 83 | try testing.expectEqual(true, encodesTo("\\", "\\\\")); | | |
| 84 | try testing.expectEqual(true, encodesTo("with\nescape", "with\\nescape")); | | |
| 85 | // with unicode | | |
| 86 | try testing.expectEqual(true, encodesTo("ą", "\\u0105")); | | |
| 87 | try testing.expectEqual(true, encodesTo("π", "\\ud83d\\ude02")); | | |
| 88 | try testing.expectEqual(true, encodesTo("withąunicodeπ", "with\\u0105unicode\\ud83d\\ude02")); | | |
| 89 | } | | |
| 90 | | | |
| 91 | /// A single token slice into the parent string. | 76 | /// A single token slice into the parent string. |
| 92 | /// | 77 | /// |
| 93 | /// Use `token.slice()` on the input at the current position to get the current slice. | 78 | /// Use `token.slice()` on the input at the current position to get the current slice. |
| ... | @@ -1100,15 +1085,6 @@ pub const StreamingParser = struct { | ... | @@ -1100,15 +1085,6 @@ pub const StreamingParser = struct { |
| 1100 | } | 1085 | } |
| 1101 | }; | 1086 | }; |
| 1102 | | 1087 | |
| 1103 | test "json.serialize issue #5959" { | | |
| 1104 | var parser: StreamingParser = undefined; | | |
| 1105 | // StreamingParser has multiple internal fields set to undefined. This causes issues when using | | |
| 1106 | // expectEqual so these are zeroed. We are testing for equality here only because this is a | | |
| 1107 | // known small test reproduction which hits the relevant LLVM issue. | | |
| 1108 | std.mem.set(u8, @ptrCast([*]u8, &parser)[0..@sizeOf(StreamingParser)], 0); | | |
| 1109 | try std.testing.expectEqual(parser, parser); | | |
| 1110 | } | | |
| 1111 | | | |
| 1112 | /// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens. | 1088 | /// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens. |
| 1113 | pub const TokenStream = struct { | 1089 | pub const TokenStream = struct { |
| 1114 | i: usize, | 1090 | i: usize, |
| ... | @@ -1164,80 +1140,6 @@ pub const TokenStream = struct { | ... | @@ -1164,80 +1140,6 @@ pub const TokenStream = struct { |
| 1164 | } | 1140 | } |
| 1165 | }; | 1141 | }; |
| 1166 | | 1142 | |
| 1167 | fn checkNext(p: *TokenStream, id: std.meta.Tag(Token)) !void { | | |
| 1168 | const token = (p.next() catch unreachable).?; | | |
| 1169 | try testing.expect(std.meta.activeTag(token) == id); | | |
| 1170 | } | | |
| 1171 | | | |
| 1172 | test "json.token" { | | |
| 1173 | const s = | | |
| 1174 | \\{ | | |
| 1175 | \\ "Image": { | | |
| 1176 | \\ "Width": 800, | | |
| 1177 | \\ "Height": 600, | | |
| 1178 | \\ "Title": "View from 15th Floor", | | |
| 1179 | \\ "Thumbnail": { | | |
| 1180 | \\ "Url": "http://www.example.com/image/481989943", | | |
| 1181 | \\ "Height": 125, | | |
| 1182 | \\ "Width": 100 | | |
| 1183 | \\ }, | | |
| 1184 | \\ "Animated" : false, | | |
| 1185 | \\ "IDs": [116, 943, 234, 38793] | | |
| 1186 | \\ } | | |
| 1187 | \\} | | |
| 1188 | ; | | |
| 1189 | | | |
| 1190 | var p = TokenStream.init(s); | | |
| 1191 | | | |
| 1192 | try checkNext(&p, .ObjectBegin); | | |
| 1193 | try checkNext(&p, .String); // Image | | |
| 1194 | try checkNext(&p, .ObjectBegin); | | |
| 1195 | try checkNext(&p, .String); // Width | | |
| 1196 | try checkNext(&p, .Number); | | |
| 1197 | try checkNext(&p, .String); // Height | | |
| 1198 | try checkNext(&p, .Number); | | |
| 1199 | try checkNext(&p, .String); // Title | | |
| 1200 | try checkNext(&p, .String); | | |
| 1201 | try checkNext(&p, .String); // Thumbnail | | |
| 1202 | try checkNext(&p, .ObjectBegin); | | |
| 1203 | try checkNext(&p, .String); // Url | | |
| 1204 | try checkNext(&p, .String); | | |
| 1205 | try checkNext(&p, .String); // Height | | |
| 1206 | try checkNext(&p, .Number); | | |
| 1207 | try checkNext(&p, .String); // Width | | |
| 1208 | try checkNext(&p, .Number); | | |
| 1209 | try checkNext(&p, .ObjectEnd); | | |
| 1210 | try checkNext(&p, .String); // Animated | | |
| 1211 | try checkNext(&p, .False); | | |
| 1212 | try checkNext(&p, .String); // IDs | | |
| 1213 | try checkNext(&p, .ArrayBegin); | | |
| 1214 | try checkNext(&p, .Number); | | |
| 1215 | try checkNext(&p, .Number); | | |
| 1216 | try checkNext(&p, .Number); | | |
| 1217 | try checkNext(&p, .Number); | | |
| 1218 | try checkNext(&p, .ArrayEnd); | | |
| 1219 | try checkNext(&p, .ObjectEnd); | | |
| 1220 | try checkNext(&p, .ObjectEnd); | | |
| 1221 | | | |
| 1222 | try testing.expect((try p.next()) == null); | | |
| 1223 | } | | |
| 1224 | | | |
| 1225 | test "json.token mismatched close" { | | |
| 1226 | var p = TokenStream.init("[102, 111, 111 }"); | | |
| 1227 | try checkNext(&p, .ArrayBegin); | | |
| 1228 | try checkNext(&p, .Number); | | |
| 1229 | try checkNext(&p, .Number); | | |
| 1230 | try checkNext(&p, .Number); | | |
| 1231 | try testing.expectError(error.UnexpectedClosingBrace, p.next()); | | |
| 1232 | } | | |
| 1233 | | | |
| 1234 | test "json.token premature object close" { | | |
| 1235 | var p = TokenStream.init("{ \"key\": }"); | | |
| 1236 | try checkNext(&p, .ObjectBegin); | | |
| 1237 | try checkNext(&p, .String); | | |
| 1238 | try testing.expectError(error.InvalidValueBegin, p.next()); | | |
| 1239 | } | | |
| 1240 | | | |
| 1241 | /// Validate a JSON string. This does not limit number precision so a decoder may not necessarily | 1143 | /// Validate a JSON string. This does not limit number precision so a decoder may not necessarily |
| 1242 | /// be able to decode the string even if this returns true. | 1144 | /// be able to decode the string even if this returns true. |
| 1243 | pub fn validate(s: []const u8) bool { | 1145 | pub fn validate(s: []const u8) bool { |
| ... | @@ -1255,15 +1157,6 @@ pub fn validate(s: []const u8) bool { | ... | @@ -1255,15 +1157,6 @@ pub fn validate(s: []const u8) bool { |
| 1255 | return p.complete; | 1157 | return p.complete; |
| 1256 | } | 1158 | } |
| 1257 | | 1159 | |
| 1258 | test "json.validate" { | | |
| 1259 | try testing.expectEqual(true, validate("{}")); | | |
| 1260 | try testing.expectEqual(true, validate("[]")); | | |
| 1261 | try testing.expectEqual(true, validate("[{[[[[{}]]]]}]")); | | |
| 1262 | try testing.expectEqual(false, validate("{]")); | | |
| 1263 | try testing.expectEqual(false, validate("[}")); | | |
| 1264 | try testing.expectEqual(false, validate("{{{{[]}}}]")); | | |
| 1265 | } | | |
| 1266 | | | |
| 1267 | const Allocator = std.mem.Allocator; | 1160 | const Allocator = std.mem.Allocator; |
| 1268 | const ArenaAllocator = std.heap.ArenaAllocator; | 1161 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 1269 | const ArrayList = std.ArrayList; | 1162 | const ArrayList = std.ArrayList; |
| ... | @@ -1352,67 +1245,6 @@ pub const Value = union(enum) { | ... | @@ -1352,67 +1245,6 @@ pub const Value = union(enum) { |
| 1352 | } | 1245 | } |
| 1353 | }; | 1246 | }; |
| 1354 | | 1247 | |
| 1355 | test "Value.jsonStringify" { | | |
| 1356 | { | | |
| 1357 | var buffer: [10]u8 = undefined; | | |
| 1358 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1359 | try @as(Value, .Null).jsonStringify(.{}, fbs.writer()); | | |
| 1360 | try testing.expectEqualSlices(u8, fbs.getWritten(), "null"); | | |
| 1361 | } | | |
| 1362 | { | | |
| 1363 | var buffer: [10]u8 = undefined; | | |
| 1364 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1365 | try (Value{ .Bool = true }).jsonStringify(.{}, fbs.writer()); | | |
| 1366 | try testing.expectEqualSlices(u8, fbs.getWritten(), "true"); | | |
| 1367 | } | | |
| 1368 | { | | |
| 1369 | var buffer: [10]u8 = undefined; | | |
| 1370 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1371 | try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.writer()); | | |
| 1372 | try testing.expectEqualSlices(u8, fbs.getWritten(), "42"); | | |
| 1373 | } | | |
| 1374 | { | | |
| 1375 | var buffer: [10]u8 = undefined; | | |
| 1376 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1377 | try (Value{ .NumberString = "43" }).jsonStringify(.{}, fbs.writer()); | | |
| 1378 | try testing.expectEqualSlices(u8, fbs.getWritten(), "43"); | | |
| 1379 | } | | |
| 1380 | { | | |
| 1381 | var buffer: [10]u8 = undefined; | | |
| 1382 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1383 | try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.writer()); | | |
| 1384 | try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01"); | | |
| 1385 | } | | |
| 1386 | { | | |
| 1387 | var buffer: [10]u8 = undefined; | | |
| 1388 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1389 | try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.writer()); | | |
| 1390 | try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\""); | | |
| 1391 | } | | |
| 1392 | { | | |
| 1393 | var buffer: [10]u8 = undefined; | | |
| 1394 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1395 | var vals = [_]Value{ | | |
| 1396 | .{ .Integer = 1 }, | | |
| 1397 | .{ .Integer = 2 }, | | |
| 1398 | .{ .NumberString = "3" }, | | |
| 1399 | }; | | |
| 1400 | try (Value{ | | |
| 1401 | .Array = Array.fromOwnedSlice(undefined, &vals), | | |
| 1402 | }).jsonStringify(.{}, fbs.writer()); | | |
| 1403 | try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]"); | | |
| 1404 | } | | |
| 1405 | { | | |
| 1406 | var buffer: [10]u8 = undefined; | | |
| 1407 | var fbs = std.io.fixedBufferStream(&buffer); | | |
| 1408 | var obj = ObjectMap.init(testing.allocator); | | |
| 1409 | defer obj.deinit(); | | |
| 1410 | try obj.putNoClobber("a", .{ .String = "b" }); | | |
| 1411 | try (Value{ .Object = obj }).jsonStringify(.{}, fbs.writer()); | | |
| 1412 | try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}"); | | |
| 1413 | } | | |
| 1414 | } | | |
| 1415 | | | |
| 1416 | /// parse tokens from a stream, returning `false` if they do not decode to `value` | 1248 | /// parse tokens from a stream, returning `false` if they do not decode to `value` |
| 1417 | fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool { | 1249 | fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool { |
| 1418 | // TODO: should be able to write this function to not require an allocator | 1250 | // TODO: should be able to write this function to not require an allocator |
| ... | @@ -1503,59 +1335,6 @@ fn skipValue(tokens: *TokenStream) SkipValueError!void { | ... | @@ -1503,59 +1335,6 @@ fn skipValue(tokens: *TokenStream) SkipValueError!void { |
| 1503 | } | 1335 | } |
| 1504 | } | 1336 | } |
| 1505 | | 1337 | |
| 1506 | test "skipValue" { | | |
| 1507 | var ts = TokenStream.init("false"); | | |
| 1508 | try skipValue(&ts); | | |
| 1509 | ts = TokenStream.init("true"); | | |
| 1510 | try skipValue(&ts); | | |
| 1511 | ts = TokenStream.init("null"); | | |
| 1512 | try skipValue(&ts); | | |
| 1513 | ts = TokenStream.init("42"); | | |
| 1514 | try skipValue(&ts); | | |
| 1515 | ts = TokenStream.init("42.0"); | | |
| 1516 | try skipValue(&ts); | | |
| 1517 | ts = TokenStream.init("\"foo\""); | | |
| 1518 | try skipValue(&ts); | | |
| 1519 | ts = TokenStream.init("[101, 111, 121]"); | | |
| 1520 | try skipValue(&ts); | | |
| 1521 | ts = TokenStream.init("{}"); | | |
| 1522 | try skipValue(&ts); | | |
| 1523 | ts = TokenStream.init("{\"foo\": \"bar\"}"); | | |
| 1524 | try skipValue(&ts); | | |
| 1525 | | | |
| 1526 | { // An absurd number of nestings | | |
| 1527 | const nestings = StreamingParser.default_max_nestings + 1; | | |
| 1528 | | | |
| 1529 | ts = TokenStream.init("[" ** nestings ++ "]" ** nestings); | | |
| 1530 | try testing.expectError(error.TooManyNestedItems, skipValue(&ts)); | | |
| 1531 | } | | |
| 1532 | | | |
| 1533 | { // Would a number token cause problems in a deeply-nested array? | | |
| 1534 | const nestings = StreamingParser.default_max_nestings; | | |
| 1535 | const deeply_nested_array = "[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings; | | |
| 1536 | | | |
| 1537 | ts = TokenStream.init(deeply_nested_array); | | |
| 1538 | try skipValue(&ts); | | |
| 1539 | | | |
| 1540 | ts = TokenStream.init("[" ++ deeply_nested_array ++ "]"); | | |
| 1541 | try testing.expectError(error.TooManyNestedItems, skipValue(&ts)); | | |
| 1542 | } | | |
| 1543 | | | |
| 1544 | // Mismatched brace/square bracket | | |
| 1545 | ts = TokenStream.init("[102, 111, 111}"); | | |
| 1546 | try testing.expectError(error.UnexpectedClosingBrace, skipValue(&ts)); | | |
| 1547 | | | |
| 1548 | { // should fail if no value found (e.g. immediate close of object) | | |
| 1549 | var empty_object = TokenStream.init("{}"); | | |
| 1550 | assert(.ObjectBegin == (try empty_object.next()).?); | | |
| 1551 | try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_object)); | | |
| 1552 | | | |
| 1553 | var empty_array = TokenStream.init("[]"); | | |
| 1554 | assert(.ArrayBegin == (try empty_array.next()).?); | | |
| 1555 | try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_array)); | | |
| 1556 | } | | |
| 1557 | } | | |
| 1558 | | | |
| 1559 | fn ParseInternalError(comptime T: type) type { | 1338 | fn ParseInternalError(comptime T: type) type { |
| 1560 | // `inferred_types` is used to avoid infinite recursion for recursive type definitions. | 1339 | // `inferred_types` is used to avoid infinite recursion for recursive type definitions. |
| 1561 | const inferred_types = [_]type{}; | 1340 | const inferred_types = [_]type{}; |
| ... | @@ -1981,440 +1760,6 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void { | ... | @@ -1981,440 +1760,6 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void { |
| 1981 | } | 1760 | } |
| 1982 | } | 1761 | } |
| 1983 | | 1762 | |
| 1984 | test "parse" { | | |
| 1985 | var ts = TokenStream.init("false"); | | |
| 1986 | try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{})); | | |
| 1987 | ts = TokenStream.init("true"); | | |
| 1988 | try testing.expectEqual(true, try parse(bool, &ts, ParseOptions{})); | | |
| 1989 | ts = TokenStream.init("1"); | | |
| 1990 | try testing.expectEqual(@as(u1, 1), try parse(u1, &ts, ParseOptions{})); | | |
| 1991 | ts = TokenStream.init("50"); | | |
| 1992 | try testing.expectError(error.Overflow, parse(u1, &ts, ParseOptions{})); | | |
| 1993 | ts = TokenStream.init("42"); | | |
| 1994 | try testing.expectEqual(@as(u64, 42), try parse(u64, &ts, ParseOptions{})); | | |
| 1995 | ts = TokenStream.init("42.0"); | | |
| 1996 | try testing.expectEqual(@as(f64, 42), try parse(f64, &ts, ParseOptions{})); | | |
| 1997 | ts = TokenStream.init("null"); | | |
| 1998 | try testing.expectEqual(@as(?bool, null), try parse(?bool, &ts, ParseOptions{})); | | |
| 1999 | ts = TokenStream.init("true"); | | |
| 2000 | try testing.expectEqual(@as(?bool, true), try parse(?bool, &ts, ParseOptions{})); | | |
| 2001 | | | |
| 2002 | ts = TokenStream.init("\"foo\""); | | |
| 2003 | try testing.expectEqual(@as([3]u8, "foo".*), try parse([3]u8, &ts, ParseOptions{})); | | |
| 2004 | ts = TokenStream.init("[102, 111, 111]"); | | |
| 2005 | try testing.expectEqual(@as([3]u8, "foo".*), try parse([3]u8, &ts, ParseOptions{})); | | |
| 2006 | ts = TokenStream.init("[]"); | | |
| 2007 | try testing.expectEqual(@as([0]u8, undefined), try parse([0]u8, &ts, ParseOptions{})); | | |
| 2008 | } | | |
| 2009 | | | |
| 2010 | test "parse into enum" { | | |
| 2011 | const T = enum(u32) { | | |
| 2012 | Foo = 42, | | |
| 2013 | Bar, | | |
| 2014 | @"with\\escape", | | |
| 2015 | }; | | |
| 2016 | var ts = TokenStream.init("\"Foo\""); | | |
| 2017 | try testing.expectEqual(@as(T, .Foo), try parse(T, &ts, ParseOptions{})); | | |
| 2018 | ts = TokenStream.init("42"); | | |
| 2019 | try testing.expectEqual(@as(T, .Foo), try parse(T, &ts, ParseOptions{})); | | |
| 2020 | ts = TokenStream.init("\"with\\\\escape\""); | | |
| 2021 | try testing.expectEqual(@as(T, .@"with\\escape"), try parse(T, &ts, ParseOptions{})); | | |
| 2022 | ts = TokenStream.init("5"); | | |
| 2023 | try testing.expectError(error.InvalidEnumTag, parse(T, &ts, ParseOptions{})); | | |
| 2024 | ts = TokenStream.init("\"Qux\""); | | |
| 2025 | try testing.expectError(error.InvalidEnumTag, parse(T, &ts, ParseOptions{})); | | |
| 2026 | } | | |
| 2027 | | | |
| 2028 | test "parse with trailing data" { | | |
| 2029 | var ts = TokenStream.init("falsed"); | | |
| 2030 | try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{ .allow_trailing_data = true })); | | |
| 2031 | ts = TokenStream.init("falsed"); | | |
| 2032 | try testing.expectError(error.InvalidTopLevelTrailing, parse(bool, &ts, ParseOptions{ .allow_trailing_data = false })); | | |
| 2033 | // trailing whitespace is okay | | |
| 2034 | ts = TokenStream.init("false \n"); | | |
| 2035 | try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{ .allow_trailing_data = false })); | | |
| 2036 | } | | |
| 2037 | | | |
| 2038 | test "parse into that allocates a slice" { | | |
| 2039 | var ts = TokenStream.init("\"foo\""); | | |
| 2040 | try testing.expectError(error.AllocatorRequired, parse([]u8, &ts, ParseOptions{})); | | |
| 2041 | | | |
| 2042 | const options = ParseOptions{ .allocator = testing.allocator }; | | |
| 2043 | { | | |
| 2044 | ts = TokenStream.init("\"foo\""); | | |
| 2045 | const r = try parse([]u8, &ts, options); | | |
| 2046 | defer parseFree([]u8, r, options); | | |
| 2047 | try testing.expectEqualSlices(u8, "foo", r); | | |
| 2048 | } | | |
| 2049 | { | | |
| 2050 | ts = TokenStream.init("[102, 111, 111]"); | | |
| 2051 | const r = try parse([]u8, &ts, options); | | |
| 2052 | defer parseFree([]u8, r, options); | | |
| 2053 | try testing.expectEqualSlices(u8, "foo", r); | | |
| 2054 | } | | |
| 2055 | { | | |
| 2056 | ts = TokenStream.init("\"with\\\\escape\""); | | |
| 2057 | const r = try parse([]u8, &ts, options); | | |
| 2058 | defer parseFree([]u8, r, options); | | |
| 2059 | try testing.expectEqualSlices(u8, "with\\escape", r); | | |
| 2060 | } | | |
| 2061 | } | | |
| 2062 | | | |
| 2063 | test "parse into tagged union" { | | |
| 2064 | { | | |
| 2065 | const T = union(enum) { | | |
| 2066 | int: i32, | | |
| 2067 | float: f64, | | |
| 2068 | string: []const u8, | | |
| 2069 | }; | | |
| 2070 | var ts = TokenStream.init("1.5"); | | |
| 2071 | try testing.expectEqual(T{ .float = 1.5 }, try parse(T, &ts, ParseOptions{})); | | |
| 2072 | } | | |
| 2073 | | | |
| 2074 | { // failing allocations should be bubbled up instantly without trying next member | | |
| 2075 | var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0); | | |
| 2076 | const options = ParseOptions{ .allocator = fail_alloc.allocator() }; | | |
| 2077 | const T = union(enum) { | | |
| 2078 | // both fields here match the input | | |
| 2079 | string: []const u8, | | |
| 2080 | array: [3]u8, | | |
| 2081 | }; | | |
| 2082 | var ts = TokenStream.init("[1,2,3]"); | | |
| 2083 | try testing.expectError(error.OutOfMemory, parse(T, &ts, options)); | | |
| 2084 | } | | |
| 2085 | | | |
| 2086 | { | | |
| 2087 | // if multiple matches possible, takes first option | | |
| 2088 | const T = union(enum) { | | |
| 2089 | x: u8, | | |
| 2090 | y: u8, | | |
| 2091 | }; | | |
| 2092 | var ts = TokenStream.init("42"); | | |
| 2093 | try testing.expectEqual(T{ .x = 42 }, try parse(T, &ts, ParseOptions{})); | | |
| 2094 | } | | |
| 2095 | | | |
| 2096 | { // needs to back out when first union member doesn't match | | |
| 2097 | const T = union(enum) { | | |
| 2098 | A: struct { x: u32 }, | | |
| 2099 | B: struct { y: u32 }, | | |
| 2100 | }; | | |
| 2101 | var ts = TokenStream.init("{\"y\":42}"); | | |
| 2102 | try testing.expectEqual(T{ .B = .{ .y = 42 } }, try parse(T, &ts, ParseOptions{})); | | |
| 2103 | } | | |
| 2104 | } | | |
| 2105 | | | |
| 2106 | test "parse union bubbles up AllocatorRequired" { | | |
| 2107 | { // string member first in union (and not matching) | | |
| 2108 | const T = union(enum) { | | |
| 2109 | string: []const u8, | | |
| 2110 | int: i32, | | |
| 2111 | }; | | |
| 2112 | var ts = TokenStream.init("42"); | | |
| 2113 | try testing.expectError(error.AllocatorRequired, parse(T, &ts, ParseOptions{})); | | |
| 2114 | } | | |
| 2115 | | | |
| 2116 | { // string member not first in union (and matching) | | |
| 2117 | const T = union(enum) { | | |
| 2118 | int: i32, | | |
| 2119 | float: f64, | | |
| 2120 | string: []const u8, | | |
| 2121 | }; | | |
| 2122 | var ts = TokenStream.init("\"foo\""); | | |
| 2123 | try testing.expectError(error.AllocatorRequired, parse(T, &ts, ParseOptions{})); | | |
| 2124 | } | | |
| 2125 | } | | |
| 2126 | | | |
| 2127 | test "parseFree descends into tagged union" { | | |
| 2128 | var fail_alloc = testing.FailingAllocator.init(testing.allocator, 1); | | |
| 2129 | const options = ParseOptions{ .allocator = fail_alloc.allocator() }; | | |
| 2130 | const T = union(enum) { | | |
| 2131 | int: i32, | | |
| 2132 | float: f64, | | |
| 2133 | string: []const u8, | | |
| 2134 | }; | | |
| 2135 | // use a string with unicode escape so we know result can't be a reference to global constant | | |
| 2136 | var ts = TokenStream.init("\"with\\u0105unicode\""); | | |
| 2137 | const r = try parse(T, &ts, options); | | |
| 2138 | try testing.expectEqual(std.meta.Tag(T).string, @as(std.meta.Tag(T), r)); | | |
| 2139 | try testing.expectEqualSlices(u8, "withąunicode", r.string); | | |
| 2140 | try testing.expectEqual(@as(usize, 0), fail_alloc.deallocations); | | |
| 2141 | parseFree(T, r, options); | | |
| 2142 | try testing.expectEqual(@as(usize, 1), fail_alloc.deallocations); | | |
| 2143 | } | | |
| 2144 | | | |
| 2145 | test "parse with comptime field" { | | |
| 2146 | { | | |
| 2147 | const T = struct { | | |
| 2148 | comptime a: i32 = 0, | | |
| 2149 | b: bool, | | |
| 2150 | }; | | |
| 2151 | var ts = TokenStream.init( | | |
| 2152 | \\{ | | |
| 2153 | \\ "a": 0, | | |
| 2154 | \\ "b": true | | |
| 2155 | \\} | | |
| 2156 | ); | | |
| 2157 | try testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &ts, ParseOptions{})); | | |
| 2158 | } | | |
| 2159 | | | |
| 2160 | { // string comptime values currently require an allocator | | |
| 2161 | const T = union(enum) { | | |
| 2162 | foo: struct { | | |
| 2163 | comptime kind: []const u8 = "boolean", | | |
| 2164 | b: bool, | | |
| 2165 | }, | | |
| 2166 | bar: struct { | | |
| 2167 | comptime kind: []const u8 = "float", | | |
| 2168 | b: f64, | | |
| 2169 | }, | | |
| 2170 | }; | | |
| 2171 | | | |
| 2172 | const options = ParseOptions{ | | |
| 2173 | .allocator = std.testing.allocator, | | |
| 2174 | }; | | |
| 2175 | | | |
| 2176 | var ts = TokenStream.init( | | |
| 2177 | \\{ | | |
| 2178 | \\ "kind": "float", | | |
| 2179 | \\ "b": 1.0 | | |
| 2180 | \\} | | |
| 2181 | ); | | |
| 2182 | const r = try parse(T, &ts, options); | | |
| 2183 | | | |
| 2184 | // check that parseFree doesn't try to free comptime fields | | |
| 2185 | parseFree(T, r, options); | | |
| 2186 | } | | |
| 2187 | } | | |
| 2188 | | | |
| 2189 | test "parse into struct with no fields" { | | |
| 2190 | const T = struct {}; | | |
| 2191 | var ts = TokenStream.init("{}"); | | |
| 2192 | try testing.expectEqual(T{}, try parse(T, &ts, ParseOptions{})); | | |
| 2193 | } | | |
| 2194 | | | |
| 2195 | test "parse into struct with misc fields" { | | |
| 2196 | @setEvalBranchQuota(10000); | | |
| 2197 | const options = ParseOptions{ .allocator = testing.allocator }; | | |
| 2198 | const T = struct { | | |
| 2199 | int: i64, | | |
| 2200 | float: f64, | | |
| 2201 | @"with\\escape": bool, | | |
| 2202 | @"withąunicodeπ": bool, | | |
| 2203 | language: []const u8, | | |
| 2204 | optional: ?bool, | | |
| 2205 | default_field: i32 = 42, | | |
| 2206 | static_array: [3]f64, | | |
| 2207 | dynamic_array: []f64, | | |
| 2208 | | | |
| 2209 | complex: struct { | | |
| 2210 | nested: []const u8, | | |
| 2211 | }, | | |
| 2212 | | | |
| 2213 | veryComplex: []struct { | | |
| 2214 | foo: []const u8, | | |
| 2215 | }, | | |
| 2216 | | | |
| 2217 | a_union: Union, | | |
| 2218 | const Union = union(enum) { | | |
| 2219 | x: u8, | | |
| 2220 | float: f64, | | |
| 2221 | string: []const u8, | | |
| 2222 | }; | | |
| 2223 | }; | | |
| 2224 | var ts = TokenStream.init( | | |
| 2225 | \\{ | | |
| 2226 | \\ "int": 420, | | |
| 2227 | \\ "float": 3.14, | | |
| 2228 | \\ "with\\escape": true, | | |
| 2229 | \\ "with\u0105unicode\ud83d\ude02": false, | | |
| 2230 | \\ "language": "zig", | | |
| 2231 | \\ "optional": null, | | |
| 2232 | \\ "static_array": [66.6, 420.420, 69.69], | | |
| 2233 | \\ "dynamic_array": [66.6, 420.420, 69.69], | | |
| 2234 | \\ "complex": { | | |
| 2235 | \\ "nested": "zig" | | |
| 2236 | \\ }, | | |
| 2237 | \\ "veryComplex": [ | | |
| 2238 | \\ { | | |
| 2239 | \\ "foo": "zig" | | |
| 2240 | \\ }, { | | |
| 2241 | \\ "foo": "rocks" | | |
| 2242 | \\ } | | |
| 2243 | \\ ], | | |
| 2244 | \\ "a_union": 100000 | | |
| 2245 | \\} | | |
| 2246 | ); | | |
| 2247 | const r = try parse(T, &ts, options); | | |
| 2248 | defer parseFree(T, r, options); | | |
| 2249 | try testing.expectEqual(@as(i64, 420), r.int); | | |
| 2250 | try testing.expectEqual(@as(f64, 3.14), r.float); | | |
| 2251 | try testing.expectEqual(true, r.@"with\\escape"); | | |
| 2252 | try testing.expectEqual(false, r.@"withąunicodeπ"); | | |
| 2253 | try testing.expectEqualSlices(u8, "zig", r.language); | | |
| 2254 | try testing.expectEqual(@as(?bool, null), r.optional); | | |
| 2255 | try testing.expectEqual(@as(i32, 42), r.default_field); | | |
| 2256 | try testing.expectEqual(@as(f64, 66.6), r.static_array[0]); | | |
| 2257 | try testing.expectEqual(@as(f64, 420.420), r.static_array[1]); | | |
| 2258 | try testing.expectEqual(@as(f64, 69.69), r.static_array[2]); | | |
| 2259 | try testing.expectEqual(@as(usize, 3), r.dynamic_array.len); | | |
| 2260 | try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]); | | |
| 2261 | try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]); | | |
| 2262 | try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]); | | |
| 2263 | try testing.expectEqualSlices(u8, r.complex.nested, "zig"); | | |
| 2264 | try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo); | | |
| 2265 | try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo); | | |
| 2266 | try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union); | | |
| 2267 | } | | |
| 2268 | | | |
| 2269 | test "parse into struct with strings and arrays with sentinels" { | | |
| 2270 | @setEvalBranchQuota(10000); | | |
| 2271 | const options = ParseOptions{ .allocator = testing.allocator }; | | |
| 2272 | const T = struct { | | |
| 2273 | language: [:0]const u8, | | |
| 2274 | language_without_sentinel: []const u8, | | |
| 2275 | data: [:99]const i32, | | |
| 2276 | simple_data: []const i32, | | |
| 2277 | }; | | |
| 2278 | var ts = TokenStream.init( | | |
| 2279 | \\{ | | |
| 2280 | \\ "language": "zig", | | |
| 2281 | \\ "language_without_sentinel": "zig again!", | | |
| 2282 | \\ "data": [1, 2, 3], | | |
| 2283 | \\ "simple_data": [4, 5, 6] | | |
| 2284 | \\} | | |
| 2285 | ); | | |
| 2286 | const r = try parse(T, &ts, options); | | |
| 2287 | defer parseFree(T, r, options); | | |
| 2288 | | | |
| 2289 | try testing.expectEqualSentinel(u8, 0, "zig", r.language); | | |
| 2290 | | | |
| 2291 | const data = [_:99]i32{ 1, 2, 3 }; | | |
| 2292 | try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data); | | |
| 2293 | | | |
| 2294 | // Make sure that arrays who aren't supposed to have a sentinel still parse without one. | | |
| 2295 | try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data))); | | |
| 2296 | try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel))); | | |
| 2297 | } | | |
| 2298 | | | |
| 2299 | test "parse into struct with duplicate field" { | | |
| 2300 | // allow allocator to detect double frees by keeping bucket in use | | |
| 2301 | const ballast = try testing.allocator.alloc(u64, 1); | | |
| 2302 | defer testing.allocator.free(ballast); | | |
| 2303 | | | |
| 2304 | const options_first = ParseOptions{ .allocator = testing.allocator, .duplicate_field_behavior = .UseFirst }; | | |
| 2305 | | | |
| 2306 | const options_last = ParseOptions{ | | |
| 2307 | .allocator = testing.allocator, | | |
| 2308 | .duplicate_field_behavior = .UseLast, | | |
| 2309 | }; | | |
| 2310 | | | |
| 2311 | const str = "{ \"a\": 1, \"a\": 0.25 }"; | | |
| 2312 | | | |
| 2313 | const T1 = struct { a: *u64 }; | | |
| 2314 | // both .UseFirst and .UseLast should fail because second "a" value isn't a u64 | | |
| 2315 | var ts = TokenStream.init(str); | | |
| 2316 | try testing.expectError(error.InvalidNumber, parse(T1, &ts, options_first)); | | |
| 2317 | ts = TokenStream.init(str); | | |
| 2318 | try testing.expectError(error.InvalidNumber, parse(T1, &ts, options_last)); | | |
| 2319 | | | |
| 2320 | const T2 = struct { a: f64 }; | | |
| 2321 | ts = TokenStream.init(str); | | |
| 2322 | try testing.expectEqual(T2{ .a = 1.0 }, try parse(T2, &ts, options_first)); | | |
| 2323 | ts = TokenStream.init(str); | | |
| 2324 | try testing.expectEqual(T2{ .a = 0.25 }, try parse(T2, &ts, options_last)); | | |
| 2325 | | | |
| 2326 | const T3 = struct { comptime a: f64 = 1.0 }; | | |
| 2327 | // .UseFirst should succeed because second "a" value is unconditionally ignored (even though != 1.0) | | |
| 2328 | const t3 = T3{ .a = 1.0 }; | | |
| 2329 | ts = TokenStream.init(str); | | |
| 2330 | try testing.expectEqual(t3, try parse(T3, &ts, options_first)); | | |
| 2331 | // .UseLast should fail because second "a" value is 0.25 which is not equal to default value of 1.0 | | |
| 2332 | ts = TokenStream.init(str); | | |
| 2333 | try testing.expectError(error.UnexpectedValue, parse(T3, &ts, options_last)); | | |
| 2334 | } | | |
| 2335 | | | |
| 2336 | test "parse into struct ignoring unknown fields" { | | |
| 2337 | const T = struct { | | |
| 2338 | int: i64, | | |
| 2339 | language: []const u8, | | |
| 2340 | }; | | |
| 2341 | | | |
| 2342 | const ops = ParseOptions{ | | |
| 2343 | .allocator = testing.allocator, | | |
| 2344 | .ignore_unknown_fields = true, | | |
| 2345 | }; | | |
| 2346 | | | |
| 2347 | var ts = TokenStream.init( | | |
| 2348 | \\{ | | |
| 2349 | \\ "int": 420, | | |
| 2350 | \\ "float": 3.14, | | |
| 2351 | \\ "with\\escape": true, | | |
| 2352 | \\ "with\u0105unicode\ud83d\ude02": false, | | |
| 2353 | \\ "optional": null, | | |
| 2354 | \\ "static_array": [66.6, 420.420, 69.69], | | |
| 2355 | \\ "dynamic_array": [66.6, 420.420, 69.69], | | |
| 2356 | \\ "complex": { | | |
| 2357 | \\ "nested": "zig" | | |
| 2358 | \\ }, | | |
| 2359 | \\ "veryComplex": [ | | |
| 2360 | \\ { | | |
| 2361 | \\ "foo": "zig" | | |
| 2362 | \\ }, { | | |
| 2363 | \\ "foo": "rocks" | | |
| 2364 | \\ } | | |
| 2365 | \\ ], | | |
| 2366 | \\ "a_union": 100000, | | |
| 2367 | \\ "language": "zig" | | |
| 2368 | \\} | | |
| 2369 | ); | | |
| 2370 | const r = try parse(T, &ts, ops); | | |
| 2371 | defer parseFree(T, r, ops); | | |
| 2372 | | | |
| 2373 | try testing.expectEqual(@as(i64, 420), r.int); | | |
| 2374 | try testing.expectEqualSlices(u8, "zig", r.language); | | |
| 2375 | } | | |
| 2376 | | | |
| 2377 | const ParseIntoRecursiveUnionDefinitionValue = union(enum) { | | |
| 2378 | integer: i64, | | |
| 2379 | array: []const ParseIntoRecursiveUnionDefinitionValue, | | |
| 2380 | }; | | |
| 2381 | | | |
| 2382 | test "parse into recursive union definition" { | | |
| 2383 | const T = struct { | | |
| 2384 | values: ParseIntoRecursiveUnionDefinitionValue, | | |
| 2385 | }; | | |
| 2386 | const ops = ParseOptions{ .allocator = testing.allocator }; | | |
| 2387 | | | |
| 2388 | var ts = TokenStream.init("{\"values\":[58]}"); | | |
| 2389 | const r = try parse(T, &ts, ops); | | |
| 2390 | defer parseFree(T, r, ops); | | |
| 2391 | | | |
| 2392 | try testing.expectEqual(@as(i64, 58), r.values.array[0].integer); | | |
| 2393 | } | | |
| 2394 | | | |
| 2395 | const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) { | | |
| 2396 | integer: i64, | | |
| 2397 | array: []const ParseIntoDoubleRecursiveUnionValueSecond, | | |
| 2398 | }; | | |
| 2399 | | | |
| 2400 | const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) { | | |
| 2401 | boolean: bool, | | |
| 2402 | array: []const ParseIntoDoubleRecursiveUnionValueFirst, | | |
| 2403 | }; | | |
| 2404 | | | |
| 2405 | test "parse into double recursive union definition" { | | |
| 2406 | const T = struct { | | |
| 2407 | values: ParseIntoDoubleRecursiveUnionValueFirst, | | |
| 2408 | }; | | |
| 2409 | const ops = ParseOptions{ .allocator = testing.allocator }; | | |
| 2410 | | | |
| 2411 | var ts = TokenStream.init("{\"values\":[[58]]}"); | | |
| 2412 | const r = try parse(T, &ts, ops); | | |
| 2413 | defer parseFree(T, r, ops); | | |
| 2414 | | | |
| 2415 | try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer); | | |
| 2416 | } | | |
| 2417 | | | |
| 2418 | /// A non-stream JSON parser which constructs a tree of Value's. | 1763 | /// A non-stream JSON parser which constructs a tree of Value's. |
| 2419 | pub const Parser = struct { | 1764 | pub const Parser = struct { |
| 2420 | allocator: Allocator, | 1765 | allocator: Allocator, |
| ... | @@ -2719,219 +2064,6 @@ pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringE | ... | @@ -2719,219 +2064,6 @@ pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringE |
| 2719 | assert(outIndex == output.len); | 2064 | assert(outIndex == output.len); |
| 2720 | } | 2065 | } |
| 2721 | | 2066 | |
| 2722 | test "json.parser.dynamic" { | | |
| 2723 | var p = Parser.init(testing.allocator, false); | | |
| 2724 | defer p.deinit(); | | |
| 2725 | | | |
| 2726 | const s = | | |
| 2727 | \\{ | | |
| 2728 | \\ "Image": { | | |
| 2729 | \\ "Width": 800, | | |
| 2730 | \\ "Height": 600, | | |
| 2731 | \\ "Title": "View from 15th Floor", | | |
| 2732 | \\ "Thumbnail": { | | |
| 2733 | \\ "Url": "http://www.example.com/image/481989943", | | |
| 2734 | \\ "Height": 125, | | |
| 2735 | \\ "Width": 100 | | |
| 2736 | \\ }, | | |
| 2737 | \\ "Animated" : false, | | |
| 2738 | \\ "IDs": [116, 943, 234, 38793], | | |
| 2739 | \\ "ArrayOfObject": [{"n": "m"}], | | |
| 2740 | \\ "double": 1.3412, | | |
| 2741 | \\ "LargeInt": 18446744073709551615 | | |
| 2742 | \\ } | | |
| 2743 | \\} | | |
| 2744 | ; | | |
| 2745 | | | |
| 2746 | var tree = try p.parse(s); | | |
| 2747 | defer tree.deinit(); | | |
| 2748 | | | |
| 2749 | var root = tree.root; | | |
| 2750 | | | |
| 2751 | var image = root.Object.get("Image").?; | | |
| 2752 | | | |
| 2753 | const width = image.Object.get("Width").?; | | |
| 2754 | try testing.expect(width.Integer == 800); | | |
| 2755 | | | |
| 2756 | const height = image.Object.get("Height").?; | | |
| 2757 | try testing.expect(height.Integer == 600); | | |
| 2758 | | | |
| 2759 | const title = image.Object.get("Title").?; | | |
| 2760 | try testing.expect(mem.eql(u8, title.String, "View from 15th Floor")); | | |
| 2761 | | | |
| 2762 | const animated = image.Object.get("Animated").?; | | |
| 2763 | try testing.expect(animated.Bool == false); | | |
| 2764 | | | |
| 2765 | const array_of_object = image.Object.get("ArrayOfObject").?; | | |
| 2766 | try testing.expect(array_of_object.Array.items.len == 1); | | |
| 2767 | | | |
| 2768 | const obj0 = array_of_object.Array.items[0].Object.get("n").?; | | |
| 2769 | try testing.expect(mem.eql(u8, obj0.String, "m")); | | |
| 2770 | | | |
| 2771 | const double = image.Object.get("double").?; | | |
| 2772 | try testing.expect(double.Float == 1.3412); | | |
| 2773 | | | |
| 2774 | const large_int = image.Object.get("LargeInt").?; | | |
| 2775 | try testing.expect(mem.eql(u8, large_int.NumberString, "18446744073709551615")); | | |
| 2776 | } | | |
| 2777 | | | |
| 2778 | test { | | |
| 2779 | _ = @import("json/test.zig"); | | |
| 2780 | _ = @import("json/write_stream.zig"); | | |
| 2781 | } | | |
| 2782 | | | |
| 2783 | test "write json then parse it" { | | |
| 2784 | var out_buffer: [1000]u8 = undefined; | | |
| 2785 | | | |
| 2786 | var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); | | |
| 2787 | const out_stream = fixed_buffer_stream.writer(); | | |
| 2788 | var jw = writeStream(out_stream, 4); | | |
| 2789 | | | |
| 2790 | try jw.beginObject(); | | |
| 2791 | | | |
| 2792 | try jw.objectField("f"); | | |
| 2793 | try jw.emitBool(false); | | |
| 2794 | | | |
| 2795 | try jw.objectField("t"); | | |
| 2796 | try jw.emitBool(true); | | |
| 2797 | | | |
| 2798 | try jw.objectField("int"); | | |
| 2799 | try jw.emitNumber(1234); | | |
| 2800 | | | |
| 2801 | try jw.objectField("array"); | | |
| 2802 | try jw.beginArray(); | | |
| 2803 | | | |
| 2804 | try jw.arrayElem(); | | |
| 2805 | try jw.emitNull(); | | |
| 2806 | | | |
| 2807 | try jw.arrayElem(); | | |
| 2808 | try jw.emitNumber(12.34); | | |
| 2809 | | | |
| 2810 | try jw.endArray(); | | |
| 2811 | | | |
| 2812 | try jw.objectField("str"); | | |
| 2813 | try jw.emitString("hello"); | | |
| 2814 | | | |
| 2815 | try jw.endObject(); | | |
| 2816 | | | |
| 2817 | var parser = Parser.init(testing.allocator, false); | | |
| 2818 | defer parser.deinit(); | | |
| 2819 | var tree = try parser.parse(fixed_buffer_stream.getWritten()); | | |
| 2820 | defer tree.deinit(); | | |
| 2821 | | | |
| 2822 | try testing.expect(tree.root.Object.get("f").?.Bool == false); | | |
| 2823 | try testing.expect(tree.root.Object.get("t").?.Bool == true); | | |
| 2824 | try testing.expect(tree.root.Object.get("int").?.Integer == 1234); | | |
| 2825 | try testing.expect(tree.root.Object.get("array").?.Array.items[0].Null == {}); | | |
| 2826 | try testing.expect(tree.root.Object.get("array").?.Array.items[1].Float == 12.34); | | |
| 2827 | try testing.expect(mem.eql(u8, tree.root.Object.get("str").?.String, "hello")); | | |
| 2828 | } | | |
| 2829 | | | |
| 2830 | fn testParse(arena_allocator: std.mem.Allocator, json_str: []const u8) !Value { | | |
| 2831 | var p = Parser.init(arena_allocator, false); | | |
| 2832 | return (try p.parse(json_str)).root; | | |
| 2833 | } | | |
| 2834 | | | |
| 2835 | test "parsing empty string gives appropriate error" { | | |
| 2836 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | | |
| 2837 | defer arena_allocator.deinit(); | | |
| 2838 | try testing.expectError(error.UnexpectedEndOfJson, testParse(arena_allocator.allocator(), "")); | | |
| 2839 | } | | |
| 2840 | | | |
| 2841 | test "integer after float has proper type" { | | |
| 2842 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | | |
| 2843 | defer arena_allocator.deinit(); | | |
| 2844 | const json = try testParse(arena_allocator.allocator(), | | |
| 2845 | \\{ | | |
| 2846 | \\ "float": 3.14, | | |
| 2847 | \\ "ints": [1, 2, 3] | | |
| 2848 | \\} | | |
| 2849 | ); | | |
| 2850 | try std.testing.expect(json.Object.get("ints").?.Array.items[0] == .Integer); | | |
| 2851 | } | | |
| 2852 | | | |
| 2853 | test "parse exponential into int" { | | |
| 2854 | const T = struct { int: i64 }; | | |
| 2855 | var ts = TokenStream.init("{ \"int\": 4.2e2 }"); | | |
| 2856 | const r = try parse(T, &ts, ParseOptions{}); | | |
| 2857 | try testing.expectEqual(@as(i64, 420), r.int); | | |
| 2858 | ts = TokenStream.init("{ \"int\": 0.042e2 }"); | | |
| 2859 | try testing.expectError(error.InvalidNumber, parse(T, &ts, ParseOptions{})); | | |
| 2860 | ts = TokenStream.init("{ \"int\": 18446744073709551616.0 }"); | | |
| 2861 | try testing.expectError(error.Overflow, parse(T, &ts, ParseOptions{})); | | |
| 2862 | } | | |
| 2863 | | | |
| 2864 | test "escaped characters" { | | |
| 2865 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | | |
| 2866 | defer arena_allocator.deinit(); | | |
| 2867 | const input = | | |
| 2868 | \\{ | | |
| 2869 | \\ "backslash": "\\", | | |
| 2870 | \\ "forwardslash": "\/", | | |
| 2871 | \\ "newline": "\n", | | |
| 2872 | \\ "carriagereturn": "\r", | | |
| 2873 | \\ "tab": "\t", | | |
| 2874 | \\ "formfeed": "\f", | | |
| 2875 | \\ "backspace": "\b", | | |
| 2876 | \\ "doublequote": "\"", | | |
| 2877 | \\ "unicode": "\u0105", | | |
| 2878 | \\ "surrogatepair": "\ud83d\ude02" | | |
| 2879 | \\} | | |
| 2880 | ; | | |
| 2881 | | | |
| 2882 | const obj = (try testParse(arena_allocator.allocator(), input)).Object; | | |
| 2883 | | | |
| 2884 | try testing.expectEqualSlices(u8, obj.get("backslash").?.String, "\\"); | | |
| 2885 | try testing.expectEqualSlices(u8, obj.get("forwardslash").?.String, "/"); | | |
| 2886 | try testing.expectEqualSlices(u8, obj.get("newline").?.String, "\n"); | | |
| 2887 | try testing.expectEqualSlices(u8, obj.get("carriagereturn").?.String, "\r"); | | |
| 2888 | try testing.expectEqualSlices(u8, obj.get("tab").?.String, "\t"); | | |
| 2889 | try testing.expectEqualSlices(u8, obj.get("formfeed").?.String, "\x0C"); | | |
| 2890 | try testing.expectEqualSlices(u8, obj.get("backspace").?.String, "\x08"); | | |
| 2891 | try testing.expectEqualSlices(u8, obj.get("doublequote").?.String, "\""); | | |
| 2892 | try testing.expectEqualSlices(u8, obj.get("unicode").?.String, "ą"); | | |
| 2893 | try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.String, "π"); | | |
| 2894 | } | | |
| 2895 | | | |
| 2896 | test "string copy option" { | | |
| 2897 | const input = | | |
| 2898 | \\{ | | |
| 2899 | \\ "noescape": "aąπ", | | |
| 2900 | \\ "simple": "\\\/\n\r\t\f\b\"", | | |
| 2901 | \\ "unicode": "\u0105", | | |
| 2902 | \\ "surrogatepair": "\ud83d\ude02" | | |
| 2903 | \\} | | |
| 2904 | ; | | |
| 2905 | | | |
| 2906 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | | |
| 2907 | defer arena_allocator.deinit(); | | |
| 2908 | const allocator = arena_allocator.allocator(); | | |
| 2909 | | | |
| 2910 | var parser = Parser.init(allocator, false); | | |
| 2911 | const tree_nocopy = try parser.parse(input); | | |
| 2912 | const obj_nocopy = tree_nocopy.root.Object; | | |
| 2913 | | | |
| 2914 | parser = Parser.init(allocator, true); | | |
| 2915 | const tree_copy = try parser.parse(input); | | |
| 2916 | const obj_copy = tree_copy.root.Object; | | |
| 2917 | | | |
| 2918 | for ([_][]const u8{ "noescape", "simple", "unicode", "surrogatepair" }) |field_name| { | | |
| 2919 | try testing.expectEqualSlices(u8, obj_nocopy.get(field_name).?.String, obj_copy.get(field_name).?.String); | | |
| 2920 | } | | |
| 2921 | | | |
| 2922 | const nocopy_addr = &obj_nocopy.get("noescape").?.String[0]; | | |
| 2923 | const copy_addr = &obj_copy.get("noescape").?.String[0]; | | |
| 2924 | | | |
| 2925 | var found_nocopy = false; | | |
| 2926 | for (input) |_, index| { | | |
| 2927 | try testing.expect(copy_addr != &input[index]); | | |
| 2928 | if (nocopy_addr == &input[index]) { | | |
| 2929 | found_nocopy = true; | | |
| 2930 | } | | |
| 2931 | } | | |
| 2932 | try testing.expect(found_nocopy); | | |
| 2933 | } | | |
| 2934 | | | |
| 2935 | pub const StringifyOptions = struct { | 2067 | pub const StringifyOptions = struct { |
| 2936 | pub const Whitespace = struct { | 2068 | pub const Whitespace = struct { |
| 2937 | /// How many indentation levels deep are we? | 2069 | /// How many indentation levels deep are we? |
| ... | @@ -3213,60 +2345,102 @@ pub fn stringify( | ... | @@ -3213,60 +2345,102 @@ pub fn stringify( |
| 3213 | unreachable; | 2345 | unreachable; |
| 3214 | } | 2346 | } |
| 3215 | | 2347 | |
| 3216 | fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { | 2348 | // Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer. |
| 3217 | const ValidationWriter = struct { | 2349 | // Caller owns returned memory. |
| 3218 | const Self = @This(); | 2350 | pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 { |
| 3219 | pub const Writer = std.io.Writer(*Self, Error, write); | 2351 | var list = std.ArrayList(u8).init(allocator); |
| 3220 | pub const Error = error{ | 2352 | errdefer list.deinit(); |
| 3221 | TooMuchData, | 2353 | try stringify(value, options, list.writer()); |
| 3222 | DifferentData, | 2354 | return list.toOwnedSlice(); |
| 3223 | }; | 2355 | } |
| 3224 | | 2356 | |
| 3225 | expected_remaining: []const u8, | 2357 | test { |
| | 2358 | if (builtin.zig_backend != .stage1) { |
| | 2359 | // https://github.com/ziglang/zig/issues/8442 |
| | 2360 | _ = @import("json/test.zig"); |
| | 2361 | } |
| | 2362 | _ = @import("json/write_stream.zig"); |
| | 2363 | } |
| 3226 | | 2364 | |
| 3227 | fn init(exp: []const u8) Self { | 2365 | test "stringify null optional fields" { |
| 3228 | return .{ .expected_remaining = exp }; | 2366 | const MyStruct = struct { |
| 3229 | } | 2367 | optional: ?[]const u8 = null, |
| | 2368 | required: []const u8 = "something", |
| | 2369 | another_optional: ?[]const u8 = null, |
| | 2370 | another_required: []const u8 = "something else", |
| | 2371 | }; |
| | 2372 | try teststringify( |
| | 2373 | \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"} |
| | 2374 | , |
| | 2375 | MyStruct{}, |
| | 2376 | StringifyOptions{}, |
| | 2377 | ); |
| | 2378 | try teststringify( |
| | 2379 | \\{"required":"something","another_required":"something else"} |
| | 2380 | , |
| | 2381 | MyStruct{}, |
| | 2382 | StringifyOptions{ .emit_null_optional_fields = false }, |
| | 2383 | ); |
| 3230 | | 2384 | |
| 3231 | pub fn writer(self: *Self) Writer { | 2385 | var ts = TokenStream.init( |
| 3232 | return .{ .context = self }; | 2386 | \\{"required":"something","another_required":"something else"} |
| 3233 | } | 2387 | ); |
| | 2388 | try std.testing.expect(try parsesTo(MyStruct, MyStruct{}, &ts, .{ |
| | 2389 | .allocator = std.testing.allocator, |
| | 2390 | })); |
| | 2391 | } |
| 3234 | | 2392 | |
| 3235 | fn write(self: *Self, bytes: []const u8) Error!usize { | 2393 | test "skipValue" { |
| 3236 | if (self.expected_remaining.len < bytes.len) { | 2394 | var ts = TokenStream.init("false"); |
| 3237 | std.debug.print( | 2395 | try skipValue(&ts); |
| 3238 | \\====== expected this output: ========= | 2396 | ts = TokenStream.init("true"); |
| 3239 | \\{s} | 2397 | try skipValue(&ts); |
| 3240 | \\======== instead found this: ========= | 2398 | ts = TokenStream.init("null"); |
| 3241 | \\{s} | 2399 | try skipValue(&ts); |
| 3242 | \\====================================== | 2400 | ts = TokenStream.init("42"); |
| 3243 | , .{ | 2401 | try skipValue(&ts); |
| 3244 | self.expected_remaining, | 2402 | ts = TokenStream.init("42.0"); |
| 3245 | bytes, | 2403 | try skipValue(&ts); |
| 3246 | }); | 2404 | ts = TokenStream.init("\"foo\""); |
| 3247 | return error.TooMuchData; | 2405 | try skipValue(&ts); |
| 3248 | } | 2406 | ts = TokenStream.init("[101, 111, 121]"); |
| 3249 | if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { | 2407 | try skipValue(&ts); |
| 3250 | std.debug.print( | 2408 | ts = TokenStream.init("{}"); |
| 3251 | \\====== expected this output: ========= | 2409 | try skipValue(&ts); |
| 3252 | \\{s} | 2410 | ts = TokenStream.init("{\"foo\": \"bar\"}"); |
| 3253 | \\======== instead found this: ========= | 2411 | try skipValue(&ts); |
| 3254 | \\{s} | | |
| 3255 | \\====================================== | | |
| 3256 | , .{ | | |
| 3257 | self.expected_remaining[0..bytes.len], | | |
| 3258 | bytes, | | |
| 3259 | }); | | |
| 3260 | return error.DifferentData; | | |
| 3261 | } | | |
| 3262 | self.expected_remaining = self.expected_remaining[bytes.len..]; | | |
| 3263 | return bytes.len; | | |
| 3264 | } | | |
| 3265 | }; | | |
| 3266 | | 2412 | |
| 3267 | var vos = ValidationWriter.init(expected); | 2413 | { // An absurd number of nestings |
| 3268 | try stringify(value, options, vos.writer()); | 2414 | const nestings = StreamingParser.default_max_nestings + 1; |
| 3269 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; | 2415 | |
| | 2416 | ts = TokenStream.init("[" ** nestings ++ "]" ** nestings); |
| | 2417 | try testing.expectError(error.TooManyNestedItems, skipValue(&ts)); |
| | 2418 | } |
| | 2419 | |
| | 2420 | { // Would a number token cause problems in a deeply-nested array? |
| | 2421 | const nestings = StreamingParser.default_max_nestings; |
| | 2422 | const deeply_nested_array = "[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings; |
| | 2423 | |
| | 2424 | ts = TokenStream.init(deeply_nested_array); |
| | 2425 | try skipValue(&ts); |
| | 2426 | |
| | 2427 | ts = TokenStream.init("[" ++ deeply_nested_array ++ "]"); |
| | 2428 | try testing.expectError(error.TooManyNestedItems, skipValue(&ts)); |
| | 2429 | } |
| | 2430 | |
| | 2431 | // Mismatched brace/square bracket |
| | 2432 | ts = TokenStream.init("[102, 111, 111}"); |
| | 2433 | try testing.expectError(error.UnexpectedClosingBrace, skipValue(&ts)); |
| | 2434 | |
| | 2435 | { // should fail if no value found (e.g. immediate close of object) |
| | 2436 | var empty_object = TokenStream.init("{}"); |
| | 2437 | assert(.ObjectBegin == (try empty_object.next()).?); |
| | 2438 | try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_object)); |
| | 2439 | |
| | 2440 | var empty_array = TokenStream.init("[]"); |
| | 2441 | assert(.ArrayBegin == (try empty_array.next()).?); |
| | 2442 | try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_array)); |
| | 2443 | } |
| 3270 | } | 2444 | } |
| 3271 | | 2445 | |
| 3272 | test "stringify basic types" { | 2446 | test "stringify basic types" { |
| ... | @@ -3423,50 +2597,74 @@ test "stringify vector" { | ... | @@ -3423,50 +2597,74 @@ test "stringify vector" { |
| 3423 | try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{}); | 2597 | try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{}); |
| 3424 | } | 2598 | } |
| 3425 | | 2599 | |
| 3426 | test "stringify null optional fields" { | 2600 | fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { |
| 3427 | const MyStruct = struct { | 2601 | const ValidationWriter = struct { |
| 3428 | optional: ?[]const u8 = null, | 2602 | const Self = @This(); |
| 3429 | required: []const u8 = "something", | 2603 | pub const Writer = std.io.Writer(*Self, Error, write); |
| 3430 | another_optional: ?[]const u8 = null, | 2604 | pub const Error = error{ |
| 3431 | another_required: []const u8 = "something else", | 2605 | TooMuchData, |
| 3432 | }; | 2606 | DifferentData, |
| 3433 | try teststringify( | 2607 | }; |
| 3434 | \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"} | | |
| 3435 | , | | |
| 3436 | MyStruct{}, | | |
| 3437 | StringifyOptions{}, | | |
| 3438 | ); | | |
| 3439 | try teststringify( | | |
| 3440 | \\{"required":"something","another_required":"something else"} | | |
| 3441 | , | | |
| 3442 | MyStruct{}, | | |
| 3443 | StringifyOptions{ .emit_null_optional_fields = false }, | | |
| 3444 | ); | | |
| 3445 | | 2608 | |
| 3446 | var ts = TokenStream.init( | 2609 | expected_remaining: []const u8, |
| 3447 | \\{"required":"something","another_required":"something else"} | | |
| 3448 | ); | | |
| 3449 | try std.testing.expect(try parsesTo(MyStruct, MyStruct{}, &ts, .{ | | |
| 3450 | .allocator = std.testing.allocator, | | |
| 3451 | })); | | |
| 3452 | } | | |
| 3453 | | 2610 | |
| 3454 | // Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer. | 2611 | fn init(exp: []const u8) Self { |
| 3455 | // Caller owns returned memory. | 2612 | return .{ .expected_remaining = exp }; |
| 3456 | pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 { | 2613 | } |
| 3457 | var list = std.ArrayList(u8).init(allocator); | | |
| 3458 | errdefer list.deinit(); | | |
| 3459 | try stringify(value, options, list.writer()); | | |
| 3460 | return list.toOwnedSlice(); | | |
| 3461 | } | | |
| 3462 | | 2614 | |
| 3463 | test "stringify alloc" { | 2615 | pub fn writer(self: *Self) Writer { |
| 3464 | const allocator = std.testing.allocator; | 2616 | return .{ .context = self }; |
| 3465 | const expected = | 2617 | } |
| 3466 | \\{"foo":"bar","answer":42,"my_friend":"sammy"} | | |
| 3467 | ; | | |
| 3468 | const actual = try stringifyAlloc(allocator, .{ .foo = "bar", .answer = 42, .my_friend = "sammy" }, .{}); | | |
| 3469 | defer allocator.free(actual); | | |
| 3470 | | 2618 | |
| 3471 | try std.testing.expectEqualStrings(expected, actual); | 2619 | fn write(self: *Self, bytes: []const u8) Error!usize { |
| | 2620 | if (self.expected_remaining.len < bytes.len) { |
| | 2621 | std.debug.print( |
| | 2622 | \\====== expected this output: ========= |
| | 2623 | \\{s} |
| | 2624 | \\======== instead found this: ========= |
| | 2625 | \\{s} |
| | 2626 | \\====================================== |
| | 2627 | , .{ |
| | 2628 | self.expected_remaining, |
| | 2629 | bytes, |
| | 2630 | }); |
| | 2631 | return error.TooMuchData; |
| | 2632 | } |
| | 2633 | if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { |
| | 2634 | std.debug.print( |
| | 2635 | \\====== expected this output: ========= |
| | 2636 | \\{s} |
| | 2637 | \\======== instead found this: ========= |
| | 2638 | \\{s} |
| | 2639 | \\====================================== |
| | 2640 | , .{ |
| | 2641 | self.expected_remaining[0..bytes.len], |
| | 2642 | bytes, |
| | 2643 | }); |
| | 2644 | return error.DifferentData; |
| | 2645 | } |
| | 2646 | self.expected_remaining = self.expected_remaining[bytes.len..]; |
| | 2647 | return bytes.len; |
| | 2648 | } |
| | 2649 | }; |
| | 2650 | |
| | 2651 | var vos = ValidationWriter.init(expected); |
| | 2652 | try stringify(value, options, vos.writer()); |
| | 2653 | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| | 2654 | } |
| | 2655 | |
| | 2656 | test "encodesTo" { |
| | 2657 | // same |
| | 2658 | try testing.expectEqual(true, encodesTo("false", "false")); |
| | 2659 | // totally different |
| | 2660 | try testing.expectEqual(false, encodesTo("false", "true")); |
| | 2661 | // different lengths |
| | 2662 | try testing.expectEqual(false, encodesTo("false", "other")); |
| | 2663 | // with escape |
| | 2664 | try testing.expectEqual(true, encodesTo("\\", "\\\\")); |
| | 2665 | try testing.expectEqual(true, encodesTo("with\nescape", "with\\nescape")); |
| | 2666 | // with unicode |
| | 2667 | try testing.expectEqual(true, encodesTo("ą", "\\u0105")); |
| | 2668 | try testing.expectEqual(true, encodesTo("π", "\\ud83d\\ude02")); |
| | 2669 | try testing.expectEqual(true, encodesTo("withąunicodeπ", "with\\u0105unicode\\ud83d\\ude02")); |
| 3472 | } | 2670 | } |