diff --git a/lib/std/json/scanner.zig b/lib/std/json/scanner.zig index e94c98809dd3729b3350459bc825f64c8082718e..80d908ac69be324d6d4aea8c65b8aecc416b759a 100644 --- a/lib/std/json/scanner.zig +++ b/lib/std/json/scanner.zig @@ -270,8 +270,11 @@ pub const Diagnostics = struct { try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start); try writer.writeAll("^\n"); - for (self.context_stack.slice()) |item| { - try writer.print(" in {s}\n", .{item}); + if (self.context_stack.len > 0) { + try writer.print("{s}\n", .{self.context_stack.slice()[0]}); + for (self.context_stack.slice()[1..]) |item| { + try writer.print(" in {s}\n", .{item}); + } } } }; diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig index 62ebb4b7297ccfd616caf8eb7a766a392732b7a6..4cccf2247a44a67cba26c0e126af231d2d130eef 100644 --- a/lib/std/json/static.zig +++ b/lib/std/json/static.zig @@ -286,7 +286,10 @@ pub fn innerParse( var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); const field_name = switch (name_token.?) { inline .string, .allocated_string => |slice| slice, - else => return error.MissingField, + else => { + if (options.diagnostics) |diag| diag.recordContext("tagged union requires a field to identify the active tag"); + return error.MissingField; + }, }; inline for (unionInfo.fields) |u_field| { @@ -301,7 +304,10 @@ pub fn innerParse( .object_begin => {}, else => |t| return typeError(options.diagnostics, t, "void payload ('{}')"), } - if (.object_end != try source.next()) return error.UnknownField; + if (.object_end != try source.next()) { + if (options.diagnostics) |diag| diag.recordContext("void payload '{}' should have no fields"); + return error.UnknownField; + } result = @unionInit(T, u_field.name, {}); } else { // Recurse. @@ -311,10 +317,14 @@ pub fn innerParse( } } else { // Didn't match anything. + if (options.diagnostics) |diag| diag.recordContext("unrecognized tag name"); return error.UnknownField; } - if (.object_end != try source.next()) return error.UnknownField; + if (.object_end != try source.next()) { + if (options.diagnostics) |diag| diag.recordContext("tagged union requires only one field"); + return error.UnknownField; + } return result.?; }, @@ -328,11 +338,23 @@ pub fn innerParse( var r: T = undefined; inline for (0..structInfo.fields.len) |i| { - if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch; + if (.array_end == try source.peekNextTokenType()) { + if (options.diagnostics) |diag| diag.recordContext(std.fmt.comptimePrint( + "tuple too short. expected length: {}", + .{structInfo.fields.len}, + )); + return error.LengthMismatch; + } r[i] = try innerParse(structInfo.fields[i].type, allocator, source, options); } - if (.array_end != try source.next()) return error.LengthMismatch; + if (.array_end != try source.next()) { + if (options.diagnostics) |diag| diag.recordContext(std.fmt.comptimePrint( + "tuple too long. expected length: {}", + .{structInfo.fields.len}, + )); + return error.LengthMismatch; + } return r; } @@ -375,7 +397,10 @@ pub fn innerParse( _ = try innerParse(field.type, allocator, source, options); break; }, - .@"error" => return error.DuplicateField, + .@"error" => { + if (options.diagnostics) |diag| diag.recordContext("duplicate field name: " ++ field.name); + return error.DuplicateField; + }, .use_last => {}, } } @@ -389,11 +414,12 @@ pub fn innerParse( if (options.ignore_unknown_fields) { try source.skipValue(); } else { + if (options.diagnostics) |diag| diag.recordContext("unrecognized field name"); return error.UnknownField; } } } - try fillDefaultStructValues(T, &r, &fields_seen); + try fillDefaultStructValues(T, options, &r, &fields_seen); return r; }, @@ -591,6 +617,7 @@ fn typeError(diagnostics: ?*Diagnostics, token: anytype, comptime expected: []co .null => prefix ++ "null", .number => prefix ++ "number", .string => prefix ++ "string", + .object_end => unreachable, // type errors happen at the start of a value. .array_end => unreachable, // type errors happen at the start of a value. .end_of_document => unreachable, // type errors happen at the start of a value. @@ -732,7 +759,7 @@ pub fn innerParseFromValue( if (!options.ignore_unknown_fields) return error.UnknownField; } } - try fillDefaultStructValues(T, &r, &fields_seen); + try fillDefaultStructValues(T, options, &r, &fields_seen); return r; }, @@ -846,13 +873,14 @@ fn sliceToEnum(comptime T: type, slice: []const u8) !T { return std.meta.intToEnum(T, n); } -fn fillDefaultStructValues(comptime T: type, r: *T, fields_seen: *[@typeInfo(T).Struct.fields.len]bool) !void { +fn fillDefaultStructValues(comptime T: type, options: ParseOptions, r: *T, fields_seen: *[@typeInfo(T).Struct.fields.len]bool) !void { inline for (@typeInfo(T).Struct.fields, 0..) |field, i| { if (!fields_seen[i]) { if (field.default_value) |default_ptr| { const default = @as(*align(1) const field.type, @ptrCast(default_ptr)).*; @field(r, field.name) = default; } else { + if (options.diagnostics) |diag| diag.recordContext("missing field: " ++ @typeName(T) ++ "." ++ field.name); return error.MissingField; } }