| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | // |
| 3 | 3 | // https://tools.ietf.org/html/rfc8259 |
| 4 | 4 | |
| 5 | const builtin = @import("builtin"); |
| 5 | 6 | const std = @import("std.zig"); |
| 6 | 7 | const debug = std.debug; |
| 7 | 8 | const assert = debug.assert; |
| ... | ... | @@ -72,22 +73,6 @@ fn encodesTo(decoded: []const u8, encoded: []const u8) bool { |
| 72 | 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 | 76 | /// A single token slice into the parent string. |
| 92 | 77 | /// |
| 93 | 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 | 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 | 1088 | /// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens. |
| 1113 | 1089 | pub const TokenStream = struct { |
| 1114 | 1090 | i: usize, |
| ... | ... | @@ -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 | 1143 | /// Validate a JSON string. This does not limit number precision so a decoder may not necessarily |
| 1242 | 1144 | /// be able to decode the string even if this returns true. |
| 1243 | 1145 | pub fn validate(s: []const u8) bool { |
| ... | ... | @@ -1255,15 +1157,6 @@ pub fn validate(s: []const u8) bool { |
| 1255 | 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 | 1160 | const Allocator = std.mem.Allocator; |
| 1268 | 1161 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 1269 | 1162 | const ArrayList = std.ArrayList; |
| ... | ... | @@ -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 | 1248 | /// parse tokens from a stream, returning `false` if they do not decode to `value` |
| 1417 | 1249 | fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool { |
| 1418 | 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 | 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 | 1338 | fn ParseInternalError(comptime T: type) type { |
| 1560 | 1339 | // `inferred_types` is used to avoid infinite recursion for recursive type definitions. |
| 1561 | 1340 | const inferred_types = [_]type{}; |
| ... | ... | @@ -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 | 1763 | /// A non-stream JSON parser which constructs a tree of Value's. |
| 2419 | 1764 | pub const Parser = struct { |
| 2420 | 1765 | allocator: Allocator, |
| ... | ... | @@ -2719,219 +2064,6 @@ pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringE |
| 2719 | 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 | 2067 | pub const StringifyOptions = struct { |
| 2936 | 2068 | pub const Whitespace = struct { |
| 2937 | 2069 | /// How many indentation levels deep are we? |
| ... | ... | @@ -3213,60 +2345,102 @@ pub fn stringify( |
| 3213 | 2345 | unreachable; |
| 3214 | 2346 | } |
| 3215 | 2347 | |
| 3216 | | fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { |
| 3217 | | const ValidationWriter = struct { |
| 3218 | | const Self = @This(); |
| 3219 | | pub const Writer = std.io.Writer(*Self, Error, write); |
| 3220 | | pub const Error = error{ |
| 3221 | | TooMuchData, |
| 3222 | | DifferentData, |
| 3223 | | }; |
| 2348 | // Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer. |
| 2349 | // Caller owns returned memory. |
| 2350 | pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 { |
| 2351 | var list = std.ArrayList(u8).init(allocator); |
| 2352 | errdefer list.deinit(); |
| 2353 | try stringify(value, options, list.writer()); |
| 2354 | return list.toOwnedSlice(); |
| 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 { |
| 3228 | | return .{ .expected_remaining = exp }; |
| 3229 | | } |
| 2365 | test "stringify null optional fields" { |
| 2366 | const MyStruct = struct { |
| 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 { |
| 3232 | | return .{ .context = self }; |
| 3233 | | } |
| 2385 | var ts = TokenStream.init( |
| 2386 | \\{"required":"something","another_required":"something else"} |
| 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 { |
| 3236 | | if (self.expected_remaining.len < bytes.len) { |
| 3237 | | std.debug.print( |
| 3238 | | \\====== expected this output: ========= |
| 3239 | | \\{s} |
| 3240 | | \\======== instead found this: ========= |
| 3241 | | \\{s} |
| 3242 | | \\====================================== |
| 3243 | | , .{ |
| 3244 | | self.expected_remaining, |
| 3245 | | bytes, |
| 3246 | | }); |
| 3247 | | return error.TooMuchData; |
| 3248 | | } |
| 3249 | | if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { |
| 3250 | | std.debug.print( |
| 3251 | | \\====== expected this output: ========= |
| 3252 | | \\{s} |
| 3253 | | \\======== instead found this: ========= |
| 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 | | }; |
| 2393 | test "skipValue" { |
| 2394 | var ts = TokenStream.init("false"); |
| 2395 | try skipValue(&ts); |
| 2396 | ts = TokenStream.init("true"); |
| 2397 | try skipValue(&ts); |
| 2398 | ts = TokenStream.init("null"); |
| 2399 | try skipValue(&ts); |
| 2400 | ts = TokenStream.init("42"); |
| 2401 | try skipValue(&ts); |
| 2402 | ts = TokenStream.init("42.0"); |
| 2403 | try skipValue(&ts); |
| 2404 | ts = TokenStream.init("\"foo\""); |
| 2405 | try skipValue(&ts); |
| 2406 | ts = TokenStream.init("[101, 111, 121]"); |
| 2407 | try skipValue(&ts); |
| 2408 | ts = TokenStream.init("{}"); |
| 2409 | try skipValue(&ts); |
| 2410 | ts = TokenStream.init("{\"foo\": \"bar\"}"); |
| 2411 | try skipValue(&ts); |
| 3266 | 2412 | |
| 3267 | | var vos = ValidationWriter.init(expected); |
| 3268 | | try stringify(value, options, vos.writer()); |
| 3269 | | if (vos.expected_remaining.len > 0) return error.NotEnoughData; |
| 2413 | { // An absurd number of nestings |
| 2414 | const nestings = StreamingParser.default_max_nestings + 1; |
| 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 | 2446 | test "stringify basic types" { |
| ... | ... | @@ -3423,50 +2597,74 @@ test "stringify vector" { |
| 3423 | 2597 | try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{}); |
| 3424 | 2598 | } |
| 3425 | 2599 | |
| 3426 | | test "stringify null optional fields" { |
| 3427 | | const MyStruct = struct { |
| 3428 | | optional: ?[]const u8 = null, |
| 3429 | | required: []const u8 = "something", |
| 3430 | | another_optional: ?[]const u8 = null, |
| 3431 | | another_required: []const u8 = "something else", |
| 3432 | | }; |
| 3433 | | try teststringify( |
| 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 | | ); |
| 2600 | fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { |
| 2601 | const ValidationWriter = struct { |
| 2602 | const Self = @This(); |
| 2603 | pub const Writer = std.io.Writer(*Self, Error, write); |
| 2604 | pub const Error = error{ |
| 2605 | TooMuchData, |
| 2606 | DifferentData, |
| 2607 | }; |
| 3445 | 2608 | |
| 3446 | | var ts = TokenStream.init( |
| 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 | | } |
| 2609 | expected_remaining: []const u8, |
| 3453 | 2610 | |
| 3454 | | // Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer. |
| 3455 | | // Caller owns returned memory. |
| 3456 | | pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 { |
| 3457 | | var list = std.ArrayList(u8).init(allocator); |
| 3458 | | errdefer list.deinit(); |
| 3459 | | try stringify(value, options, list.writer()); |
| 3460 | | return list.toOwnedSlice(); |
| 3461 | | } |
| 2611 | fn init(exp: []const u8) Self { |
| 2612 | return .{ .expected_remaining = exp }; |
| 2613 | } |
| 3462 | 2614 | |
| 3463 | | test "stringify alloc" { |
| 3464 | | const allocator = std.testing.allocator; |
| 3465 | | const expected = |
| 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); |
| 2615 | pub fn writer(self: *Self) Writer { |
| 2616 | return .{ .context = self }; |
| 2617 | } |
| 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 | } |