diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 5cbdc57832..a9895e270a 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1757,6 +1757,7 @@ test "parseUnsigned" { } pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; +pub const ParseFloatError = @import("fmt/parse_float.zig").ParseFloatError; pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat; test { diff --git a/lib/std/fmt/parse_float.zig b/lib/std/fmt/parse_float.zig index 6178398b17..ed6224b4b9 100644 --- a/lib/std/fmt/parse_float.zig +++ b/lib/std/fmt/parse_float.zig @@ -349,7 +349,9 @@ fn caseInEql(a: []const u8, b: []const u8) bool { return true; } -pub fn parseFloat(comptime T: type, s: []const u8) !T { +pub const ParseFloatError = error{InvalidCharacter}; + +pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T { if (s.len == 0 or (s.len == 1 and (s[0] == '+' or s[0] == '-'))) { return error.InvalidCharacter; } diff --git a/lib/std/json.zig b/lib/std/json.zig index 7aac61681d..656c91c080 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1468,7 +1468,9 @@ pub const ParseOptions = struct { allow_trailing_data: bool = false, }; -fn skipValue(tokens: *TokenStream) !void { +const SkipValueError = error{UnexpectedJsonDepth} || TokenStream.Error; + +fn skipValue(tokens: *TokenStream) SkipValueError!void { const original_depth = tokens.stackUsed(); // Return an error if no value is found @@ -1530,7 +1532,84 @@ test "skipValue" { } } -fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: ParseOptions) !T { +fn ParseInternalError(comptime T: type) type { + // `inferred_types` is used to avoid infinite recursion for recursive type definitions. + const inferred_types = [_]type{}; + return ParseInternalErrorImpl(T, &inferred_types); +} + +fn ParseInternalErrorImpl(comptime T: type, comptime inferred_types: []const type) type { + for (inferred_types) |ty| { + if (T == ty) return error{}; + } + + switch (@typeInfo(T)) { + .Bool => return error{UnexpectedToken}, + .Float, .ComptimeFloat => return error{UnexpectedToken} || std.fmt.ParseFloatError, + .Int, .ComptimeInt => { + return error{ UnexpectedToken, InvalidNumber, Overflow } || + std.fmt.ParseIntError || std.fmt.ParseFloatError; + }, + .Optional => |optionalInfo| { + return ParseInternalErrorImpl(optionalInfo.child, inferred_types ++ [_]type{T}); + }, + .Enum => return error{ UnexpectedToken, InvalidEnumTag } || std.fmt.ParseIntError || + std.meta.IntToEnumError || std.meta.IntToEnumError, + .Union => |unionInfo| { + if (unionInfo.tag_type) |_| { + var errors = error{NoUnionMembersMatched}; + for (unionInfo.fields) |u_field| { + errors = errors || ParseInternalErrorImpl(u_field.field_type, inferred_types ++ [_]type{T}); + } + return errors; + } else { + @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'"); + } + }, + .Struct => |structInfo| { + var errors = error{ + DuplicateJSONField, + UnexpectedEndOfJson, + UnexpectedToken, + UnexpectedValue, + UnknownField, + MissingField, + } || SkipValueError || TokenStream.Error; + for (structInfo.fields) |field| { + errors = errors || ParseInternalErrorImpl(field.field_type, inferred_types ++ [_]type{T}); + } + return errors; + }, + .Array => |arrayInfo| { + return error{ UnexpectedEndOfJson, UnexpectedToken } || TokenStream.Error || + UnescapeValidStringError || + ParseInternalErrorImpl(arrayInfo.child, inferred_types ++ [_]type{T}); + }, + .Pointer => |ptrInfo| { + var errors = error{AllocatorRequired} || std.mem.Allocator.Error; + switch (ptrInfo.size) { + .One => { + return errors || ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T}); + }, + .Slice => { + return errors || error{ UnexpectedEndOfJson, UnexpectedToken } || + ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T}) || + UnescapeValidStringError || TokenStream.Error; + }, + else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"), + } + }, + else => return error{}, + } + unreachable; +} + +fn parseInternal( + comptime T: type, + token: Token, + tokens: *TokenStream, + options: ParseOptions, +) ParseInternalError(T)!T { switch (@typeInfo(T)) { .Bool => { return switch (token) { @@ -1794,7 +1873,11 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: unreachable; } -pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T { +pub fn ParseError(comptime T: type) type { + return ParseInternalError(T) || error{UnexpectedEndOfJson} || TokenStream.Error; +} + +pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) ParseError(T)!T { const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson; const r = try parseInternal(T, token, tokens, options); errdefer parseFree(T, r, options); @@ -2181,6 +2264,45 @@ test "parse into struct ignoring unknown fields" { try testing.expectEqualSlices(u8, "zig", r.language); } +const ParseIntoRecursiveUnionDefinitionValue = union(enum) { + integer: i64, + array: []const ParseIntoRecursiveUnionDefinitionValue, +}; + +test "parse into recursive union definition" { + const T = struct { + values: ParseIntoRecursiveUnionDefinitionValue, + }; + const ops = ParseOptions{ .allocator = testing.allocator }; + + const r = try parse(T, &std.json.TokenStream.init("{\"values\":[58]}"), ops); + defer parseFree(T, r, ops); + + 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 ops = ParseOptions{ .allocator = testing.allocator }; + + const r = try parse(T, &std.json.TokenStream.init("{\"values\":[[58]]}"), ops); + defer parseFree(T, r, ops); + + try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer); +} + /// A non-stream JSON parser which constructs a tree of Value's. pub const Parser = struct { allocator: *Allocator, @@ -2418,10 +2540,12 @@ pub const Parser = struct { } }; +pub const UnescapeValidStringError = error{InvalidUnicodeHexSymbol}; + /// Unescape a JSON string /// Only to be used on strings already validated by the parser /// (note the unreachable statements and lack of bounds checking) -pub fn unescapeValidString(output: []u8, input: []const u8) !void { +pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringError!void { var inIndex: usize = 0; var outIndex: usize = 0;