mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
438 lines
16 KiB
Zig
438 lines
16 KiB
Zig
const std = @import("std");
|
|
const testing = std.testing;
|
|
|
|
const parseFromSlice = @import("./static.zig").parseFromSlice;
|
|
const parseFromTokenSource = @import("./static.zig").parseFromTokenSource;
|
|
const parseFree = @import("./static.zig").parseFree;
|
|
const ParseOptions = @import("./static.zig").ParseOptions;
|
|
const JsonScanner = @import("./scanner.zig").Scanner;
|
|
const jsonReader = @import("./scanner.zig").reader;
|
|
|
|
test "parse" {
|
|
try testing.expectEqual(false, try parseFromSlice(bool, testing.allocator, "false", .{}));
|
|
try testing.expectEqual(true, try parseFromSlice(bool, testing.allocator, "true", .{}));
|
|
try testing.expectEqual(@as(u1, 1), try parseFromSlice(u1, testing.allocator, "1", .{}));
|
|
try testing.expectError(error.Overflow, parseFromSlice(u1, testing.allocator, "50", .{}));
|
|
try testing.expectEqual(@as(u64, 42), try parseFromSlice(u64, testing.allocator, "42", .{}));
|
|
try testing.expectEqual(@as(f64, 42), try parseFromSlice(f64, testing.allocator, "42.0", .{}));
|
|
try testing.expectEqual(@as(?bool, null), try parseFromSlice(?bool, testing.allocator, "null", .{}));
|
|
try testing.expectEqual(@as(?bool, true), try parseFromSlice(?bool, testing.allocator, "true", .{}));
|
|
|
|
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSlice([3]u8, testing.allocator, "\"foo\"", .{}));
|
|
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSlice([3]u8, testing.allocator, "[102, 111, 111]", .{}));
|
|
try testing.expectEqual(@as([0]u8, undefined), try parseFromSlice([0]u8, testing.allocator, "[]", .{}));
|
|
|
|
try testing.expectEqual(@as(u64, 12345678901234567890), try parseFromSlice(u64, testing.allocator, "\"12345678901234567890\"", .{}));
|
|
try testing.expectEqual(@as(f64, 123.456), try parseFromSlice(f64, testing.allocator, "\"123.456\"", .{}));
|
|
}
|
|
|
|
test "parse into enum" {
|
|
const T = enum(u32) {
|
|
Foo = 42,
|
|
Bar,
|
|
@"with\\escape",
|
|
};
|
|
try testing.expectEqual(@as(T, .Foo), try parseFromSlice(T, testing.allocator, "\"Foo\"", .{}));
|
|
try testing.expectEqual(@as(T, .Foo), try parseFromSlice(T, testing.allocator, "42", .{}));
|
|
try testing.expectEqual(@as(T, .@"with\\escape"), try parseFromSlice(T, testing.allocator, "\"with\\\\escape\"", .{}));
|
|
try testing.expectError(error.InvalidEnumTag, parseFromSlice(T, testing.allocator, "5", .{}));
|
|
try testing.expectError(error.InvalidEnumTag, parseFromSlice(T, testing.allocator, "\"Qux\"", .{}));
|
|
}
|
|
|
|
test "parse into that allocates a slice" {
|
|
{
|
|
// string as string
|
|
const r = try parseFromSlice([]u8, testing.allocator, "\"foo\"", .{});
|
|
defer parseFree([]u8, testing.allocator, r);
|
|
try testing.expectEqualSlices(u8, "foo", r);
|
|
}
|
|
{
|
|
// string as array of u8 integers
|
|
const r = try parseFromSlice([]u8, testing.allocator, "[102, 111, 111]", .{});
|
|
defer parseFree([]u8, testing.allocator, r);
|
|
try testing.expectEqualSlices(u8, "foo", r);
|
|
}
|
|
{
|
|
const r = try parseFromSlice([]u8, testing.allocator, "\"with\\\\escape\"", .{});
|
|
defer parseFree([]u8, testing.allocator, r);
|
|
try testing.expectEqualSlices(u8, "with\\escape", r);
|
|
}
|
|
}
|
|
|
|
test "parse into sentinel slice" {
|
|
const result = try parseFromSlice([:0]const u8, testing.allocator, "\"\\n\"", .{});
|
|
defer parseFree([:0]const u8, testing.allocator, result);
|
|
try testing.expect(std.mem.eql(u8, result, "\n"));
|
|
}
|
|
|
|
test "parse into tagged union" {
|
|
const T = union(enum) {
|
|
nothing,
|
|
int: i32,
|
|
float: f64,
|
|
string: []const u8,
|
|
};
|
|
try testing.expectEqual(T{ .float = 1.5 }, try parseFromSlice(T, testing.allocator, "{\"float\":1.5}", .{}));
|
|
try testing.expectEqual(T{ .int = 1 }, try parseFromSlice(T, testing.allocator, "{\"int\":1}", .{}));
|
|
try testing.expectEqual(T{ .nothing = {} }, try parseFromSlice(T, testing.allocator, "{\"nothing\":{}}", .{}));
|
|
}
|
|
|
|
test "parse into tagged union errors" {
|
|
const T = union(enum) {
|
|
nothing,
|
|
int: i32,
|
|
float: f64,
|
|
string: []const u8,
|
|
};
|
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "42", .{}));
|
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{}", .{}));
|
|
try testing.expectError(error.UnknownField, parseFromSlice(T, testing.allocator, "{\"bogus\":1}", .{}));
|
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"int\":1, \"int\":1", .{}));
|
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"int\":1, \"float\":1.0}", .{}));
|
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"nothing\":null}", .{}));
|
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"nothing\":{\"no\":0}}", .{}));
|
|
|
|
// Allocator failure
|
|
var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0);
|
|
const failing_allocator = fail_alloc.allocator();
|
|
try testing.expectError(error.OutOfMemory, parseFromSlice(T, failing_allocator, "{\"string\"\"foo\"}", .{}));
|
|
}
|
|
|
|
test "parseFree descends into tagged union" {
|
|
const T = union(enum) {
|
|
nothing,
|
|
int: i32,
|
|
float: f64,
|
|
string: []const u8,
|
|
};
|
|
const r = try parseFromSlice(T, testing.allocator, "{\"string\":\"foo\"}", .{});
|
|
try testing.expectEqualSlices(u8, "foo", r.string);
|
|
parseFree(T, testing.allocator, r);
|
|
}
|
|
|
|
test "parse into struct with no fields" {
|
|
const T = struct {};
|
|
try testing.expectEqual(T{}, try parseFromSlice(T, testing.allocator, "{}", .{}));
|
|
}
|
|
|
|
const test_const_value: usize = 123;
|
|
|
|
test "parse into struct with default const pointer field" {
|
|
const T = struct { a: *const usize = &test_const_value };
|
|
try testing.expectEqual(T{}, try parseFromSlice(T, testing.allocator, "{}", .{}));
|
|
}
|
|
|
|
const test_default_usize: usize = 123;
|
|
const test_default_usize_ptr: *align(1) const usize = &test_default_usize;
|
|
const test_default_str: []const u8 = "test str";
|
|
const test_default_str_slice: [2][]const u8 = [_][]const u8{
|
|
"test1",
|
|
"test2",
|
|
};
|
|
|
|
test "freeing parsed structs with pointers to default values" {
|
|
const T = struct {
|
|
int: *const usize = &test_default_usize,
|
|
int_ptr: *allowzero align(1) const usize = test_default_usize_ptr,
|
|
str: []const u8 = test_default_str,
|
|
str_slice: []const []const u8 = &test_default_str_slice,
|
|
};
|
|
|
|
const parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
|
|
try testing.expectEqual(T{}, parsed);
|
|
// This will panic if it tries to free global constants:
|
|
parseFree(T, testing.allocator, parsed);
|
|
}
|
|
|
|
test "parse into struct where destination and source lengths mismatch" {
|
|
const T = struct { a: [2]u8 };
|
|
try testing.expectError(error.LengthMismatch, parseFromSlice(T, testing.allocator, "{\"a\": \"bbb\"}", .{}));
|
|
}
|
|
|
|
test "parse into struct with misc fields" {
|
|
const T = struct {
|
|
int: i64,
|
|
float: f64,
|
|
@"with\\escape": bool,
|
|
@"withąunicode😂": bool,
|
|
language: []const u8,
|
|
optional: ?bool,
|
|
default_field: i32 = 42,
|
|
static_array: [3]f64,
|
|
dynamic_array: []f64,
|
|
|
|
complex: struct {
|
|
nested: []const u8,
|
|
},
|
|
|
|
veryComplex: []struct {
|
|
foo: []const u8,
|
|
},
|
|
|
|
a_union: Union,
|
|
const Union = union(enum) {
|
|
x: u8,
|
|
float: f64,
|
|
string: []const u8,
|
|
};
|
|
};
|
|
var document_str =
|
|
\\{
|
|
\\ "int": 420,
|
|
\\ "float": 3.14,
|
|
\\ "with\\escape": true,
|
|
\\ "with\u0105unicode\ud83d\ude02": false,
|
|
\\ "language": "zig",
|
|
\\ "optional": null,
|
|
\\ "static_array": [66.6, 420.420, 69.69],
|
|
\\ "dynamic_array": [66.6, 420.420, 69.69],
|
|
\\ "complex": {
|
|
\\ "nested": "zig"
|
|
\\ },
|
|
\\ "veryComplex": [
|
|
\\ {
|
|
\\ "foo": "zig"
|
|
\\ }, {
|
|
\\ "foo": "rocks"
|
|
\\ }
|
|
\\ ],
|
|
\\ "a_union": {
|
|
\\ "float": 100000
|
|
\\ }
|
|
\\}
|
|
;
|
|
const r = try parseFromSlice(T, testing.allocator, document_str, .{});
|
|
defer parseFree(T, testing.allocator, r);
|
|
try testing.expectEqual(@as(i64, 420), r.int);
|
|
try testing.expectEqual(@as(f64, 3.14), r.float);
|
|
try testing.expectEqual(true, r.@"with\\escape");
|
|
try testing.expectEqual(false, r.@"withąunicode😂");
|
|
try testing.expectEqualSlices(u8, "zig", r.language);
|
|
try testing.expectEqual(@as(?bool, null), r.optional);
|
|
try testing.expectEqual(@as(i32, 42), r.default_field);
|
|
try testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
|
|
try testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
|
|
try testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
|
|
try testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
|
|
try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
|
|
try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
|
|
try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
|
|
try testing.expectEqualSlices(u8, r.complex.nested, "zig");
|
|
try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
|
|
try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
|
|
try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
|
|
}
|
|
|
|
test "parse into struct with strings and arrays with sentinels" {
|
|
const T = struct {
|
|
language: [:0]const u8,
|
|
language_without_sentinel: []const u8,
|
|
data: [:99]const i32,
|
|
simple_data: []const i32,
|
|
};
|
|
var document_str =
|
|
\\{
|
|
\\ "language": "zig",
|
|
\\ "language_without_sentinel": "zig again!",
|
|
\\ "data": [1, 2, 3],
|
|
\\ "simple_data": [4, 5, 6]
|
|
\\}
|
|
;
|
|
const r = try parseFromSlice(T, testing.allocator, document_str, .{});
|
|
defer parseFree(T, testing.allocator, r);
|
|
|
|
try testing.expectEqualSentinel(u8, 0, "zig", r.language);
|
|
|
|
const data = [_:99]i32{ 1, 2, 3 };
|
|
try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data);
|
|
|
|
// Make sure that arrays who aren't supposed to have a sentinel still parse without one.
|
|
try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data)));
|
|
try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel)));
|
|
}
|
|
|
|
test "parse into struct with duplicate field" {
|
|
// allow allocator to detect double frees by keeping bucket in use
|
|
const ballast = try testing.allocator.alloc(u64, 1);
|
|
defer testing.allocator.free(ballast);
|
|
|
|
const options_first = ParseOptions{ .duplicate_field_behavior = .use_first };
|
|
const options_last = ParseOptions{ .duplicate_field_behavior = .use_last };
|
|
|
|
const str = "{ \"a\": 1, \"a\": 0.25 }";
|
|
|
|
const T1 = struct { a: *u64 };
|
|
// both .use_first and .use_last should fail because second "a" value isn't a u64
|
|
try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_first));
|
|
try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_last));
|
|
|
|
const T2 = struct { a: f64 };
|
|
try testing.expectEqual(T2{ .a = 1.0 }, try parseFromSlice(T2, testing.allocator, str, options_first));
|
|
try testing.expectEqual(T2{ .a = 0.25 }, try parseFromSlice(T2, testing.allocator, str, options_last));
|
|
}
|
|
|
|
test "parse into struct ignoring unknown fields" {
|
|
const T = struct {
|
|
int: i64,
|
|
language: []const u8,
|
|
};
|
|
|
|
var str =
|
|
\\{
|
|
\\ "int": 420,
|
|
\\ "float": 3.14,
|
|
\\ "with\\escape": true,
|
|
\\ "with\u0105unicode\ud83d\ude02": false,
|
|
\\ "optional": null,
|
|
\\ "static_array": [66.6, 420.420, 69.69],
|
|
\\ "dynamic_array": [66.6, 420.420, 69.69],
|
|
\\ "complex": {
|
|
\\ "nested": "zig"
|
|
\\ },
|
|
\\ "veryComplex": [
|
|
\\ {
|
|
\\ "foo": "zig"
|
|
\\ }, {
|
|
\\ "foo": "rocks"
|
|
\\ }
|
|
\\ ],
|
|
\\ "a_union": {
|
|
\\ "float": 100000
|
|
\\ },
|
|
\\ "language": "zig"
|
|
\\}
|
|
;
|
|
const r = try parseFromSlice(T, testing.allocator, str, .{ .ignore_unknown_fields = true });
|
|
defer parseFree(T, testing.allocator, r);
|
|
|
|
try testing.expectEqual(@as(i64, 420), r.int);
|
|
try testing.expectEqualSlices(u8, "zig", r.language);
|
|
}
|
|
|
|
test "parse into tuple" {
|
|
const Union = union(enum) {
|
|
char: u8,
|
|
float: f64,
|
|
string: []const u8,
|
|
};
|
|
const T = std.meta.Tuple(&.{
|
|
i64,
|
|
f64,
|
|
bool,
|
|
[]const u8,
|
|
?bool,
|
|
struct {
|
|
foo: i32,
|
|
bar: []const u8,
|
|
},
|
|
std.meta.Tuple(&.{ u8, []const u8, u8 }),
|
|
Union,
|
|
});
|
|
var str =
|
|
\\[
|
|
\\ 420,
|
|
\\ 3.14,
|
|
\\ true,
|
|
\\ "zig",
|
|
\\ null,
|
|
\\ {
|
|
\\ "foo": 1,
|
|
\\ "bar": "zero"
|
|
\\ },
|
|
\\ [4, "två", 42],
|
|
\\ {"float": 12.34}
|
|
\\]
|
|
;
|
|
const r = try parseFromSlice(T, testing.allocator, str, .{});
|
|
defer parseFree(T, testing.allocator, r);
|
|
try testing.expectEqual(@as(i64, 420), r[0]);
|
|
try testing.expectEqual(@as(f64, 3.14), r[1]);
|
|
try testing.expectEqual(true, r[2]);
|
|
try testing.expectEqualSlices(u8, "zig", r[3]);
|
|
try testing.expectEqual(@as(?bool, null), r[4]);
|
|
try testing.expectEqual(@as(i32, 1), r[5].foo);
|
|
try testing.expectEqualSlices(u8, "zero", r[5].bar);
|
|
try testing.expectEqual(@as(u8, 4), r[6][0]);
|
|
try testing.expectEqualSlices(u8, "två", r[6][1]);
|
|
try testing.expectEqual(@as(u8, 42), r[6][2]);
|
|
try testing.expectEqual(Union{ .float = 12.34 }, r[7]);
|
|
}
|
|
|
|
const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
|
|
integer: i64,
|
|
array: []const ParseIntoRecursiveUnionDefinitionValue,
|
|
};
|
|
|
|
test "parse into recursive union definition" {
|
|
const T = struct {
|
|
values: ParseIntoRecursiveUnionDefinitionValue,
|
|
};
|
|
|
|
const r = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"integer\":58}]}}", .{});
|
|
defer parseFree(T, testing.allocator, r);
|
|
|
|
try testing.expectEqual(@as(i64, 58), r.values.array[0].integer);
|
|
}
|
|
|
|
const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
|
|
integer: i64,
|
|
array: []const ParseIntoDoubleRecursiveUnionValueSecond,
|
|
};
|
|
|
|
const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
|
|
boolean: bool,
|
|
array: []const ParseIntoDoubleRecursiveUnionValueFirst,
|
|
};
|
|
|
|
test "parse into double recursive union definition" {
|
|
const T = struct {
|
|
values: ParseIntoDoubleRecursiveUnionValueFirst,
|
|
};
|
|
|
|
const r = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"array\":[{\"integer\":58}]}]}}", .{});
|
|
defer parseFree(T, testing.allocator, r);
|
|
|
|
try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer);
|
|
}
|
|
|
|
test "parse exponential into int" {
|
|
const T = struct { int: i64 };
|
|
const r = try parseFromSlice(T, testing.allocator, "{ \"int\": 4.2e2 }", .{});
|
|
try testing.expectEqual(@as(i64, 420), r.int);
|
|
try testing.expectError(error.InvalidNumber, parseFromSlice(T, testing.allocator, "{ \"int\": 0.042e2 }", .{}));
|
|
try testing.expectError(error.Overflow, parseFromSlice(T, testing.allocator, "{ \"int\": 18446744073709551616.0 }", .{}));
|
|
}
|
|
|
|
test "parseFromTokenSource" {
|
|
var scanner = JsonScanner.initCompleteInput(testing.allocator, "123");
|
|
defer scanner.deinit();
|
|
try testing.expectEqual(@as(u32, 123), try parseFromTokenSource(u32, testing.allocator, &scanner, .{}));
|
|
|
|
var stream = std.io.fixedBufferStream("123");
|
|
var json_reader = jsonReader(std.testing.allocator, stream.reader());
|
|
defer json_reader.deinit();
|
|
try testing.expectEqual(@as(u32, 123), try parseFromTokenSource(u32, testing.allocator, &json_reader, .{}));
|
|
}
|
|
|
|
test "max_value_len" {
|
|
try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 }));
|
|
}
|
|
|
|
test "parse into vector" {
|
|
const T = struct {
|
|
vec_i32: @Vector(4, i32),
|
|
vec_f32: @Vector(2, f32),
|
|
};
|
|
var s =
|
|
\\{
|
|
\\ "vec_f32": [1.5, 2.5],
|
|
\\ "vec_i32": [4, 5, 6, 7]
|
|
\\}
|
|
;
|
|
const r = try parseFromSlice(T, testing.allocator, s, .{});
|
|
defer parseFree(T, testing.allocator, r);
|
|
try testing.expectApproxEqAbs(@as(f32, 1.5), r.vec_f32[0], 0.0000001);
|
|
try testing.expectApproxEqAbs(@as(f32, 2.5), r.vec_f32[1], 0.0000001);
|
|
try testing.expectEqual(@Vector(4, i32){ 4, 5, 6, 7 }, r.vec_i32);
|
|
}
|