throw errors on coercion rather than crashing on invalid input
All checks were successful
Generic zig build / build (push) Successful in 2m5s
All checks were successful
Generic zig build / build (push) Successful in 2m5s
This commit is contained in:
parent
8048de5e81
commit
ea2c35825d
1 changed files with 65 additions and 1 deletions
66
src/srf.zig
66
src/srf.zig
|
|
@ -504,12 +504,14 @@ fn coerce(name: []const u8, comptime T: type, val: ?Value, options: CoercionOpti
|
||||||
const float = try parseFloat(f64, val.?.string);
|
const float = try parseFloat(f64, val.?.string);
|
||||||
return .initFree(@as(T, @trunc(float)));
|
return .initFree(@as(T, @trunc(float)));
|
||||||
}
|
}
|
||||||
|
if (val.? != .number) return error.IntNotNumberType;
|
||||||
return .init(@as(T, @intFromFloat(val.?.number)));
|
return .init(@as(T, @intFromFloat(val.?.number)));
|
||||||
},
|
},
|
||||||
.float => {
|
.float => {
|
||||||
if (options.strings_to_numbers and val.? == .string) {
|
if (options.strings_to_numbers and val.? == .string) {
|
||||||
return .initFree(try parseFloat(T, val.?.string));
|
return .initFree(try parseFloat(T, val.?.string));
|
||||||
}
|
}
|
||||||
|
if (val.? != .number) return error.FloatNotNumberType;
|
||||||
return .init(@as(T, @floatCast(val.?.number)));
|
return .init(@as(T, @floatCast(val.?.number)));
|
||||||
},
|
},
|
||||||
.bool => return switch (val.?) {
|
.bool => return switch (val.?) {
|
||||||
|
|
@ -522,7 +524,10 @@ fn coerce(name: []const u8, comptime T: type, val: ?Value, options: CoercionOpti
|
||||||
return error.StringValueOfBooleanMustBetrueOrfalse,
|
return error.StringValueOfBooleanMustBetrueOrfalse,
|
||||||
else => return error.BooleanNotBooleanOrString,
|
else => return error.BooleanNotBooleanOrString,
|
||||||
},
|
},
|
||||||
.@"enum" => return .initFree(std.meta.stringToEnum(T, val.?.string).?),
|
.@"enum" => {
|
||||||
|
if (val.? != .string) return error.EnumValueNotString;
|
||||||
|
return .initFree(std.meta.stringToEnum(T, val.?.string) orelse return error.StringValueNotValidEnumMember);
|
||||||
|
},
|
||||||
.array => return error.NotImplemented,
|
.array => return error.NotImplemented,
|
||||||
.@"struct", .@"union" => {
|
.@"struct", .@"union" => {
|
||||||
if (std.meta.hasMethod(T, "srfParse")) {
|
if (std.meta.hasMethod(T, "srfParse")) {
|
||||||
|
|
@ -2569,3 +2574,62 @@ test "Tombstone reason truncates at an inline '#' (Directive comment stripping)"
|
||||||
try std.testing.expect(ri.tombstoned.?.isForever());
|
try std.testing.expect(ri.tombstoned.?.isForever());
|
||||||
try std.testing.expectEqualStrings("gone", ri.tombstoned.?.reason().?);
|
try std.testing.expectEqualStrings("gone", ri.tombstoned.?.reason().?);
|
||||||
}
|
}
|
||||||
|
test "coerce: unknown enum member is an error" {
|
||||||
|
const data =
|
||||||
|
\\#!srfv1
|
||||||
|
\\kind::gamma
|
||||||
|
;
|
||||||
|
var reader = std.Io.Reader.fixed(data);
|
||||||
|
var ri = try iterator(&reader, std.testing.allocator, .{});
|
||||||
|
defer ri.deinit();
|
||||||
|
const fi = (try ri.next()).?;
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.StringValueNotValidEnumMember,
|
||||||
|
fi.to(struct { kind: enum { alpha, beta } }, .{}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "coerce: non-string enum value is an error" {
|
||||||
|
const data =
|
||||||
|
\\#!srfv1
|
||||||
|
\\kind:num:1
|
||||||
|
;
|
||||||
|
var reader = std.Io.Reader.fixed(data);
|
||||||
|
var ri = try iterator(&reader, std.testing.allocator, .{});
|
||||||
|
defer ri.deinit();
|
||||||
|
const fi = (try ri.next()).?;
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.EnumValueNotString,
|
||||||
|
fi.to(struct { kind: enum { alpha, beta } }, .{}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "coerce: valid enum member still round-trips" {
|
||||||
|
const data =
|
||||||
|
\\#!srfv1
|
||||||
|
\\kind::beta
|
||||||
|
;
|
||||||
|
var reader = std.Io.Reader.fixed(data);
|
||||||
|
var ri = try iterator(&reader, std.testing.allocator, .{});
|
||||||
|
defer ri.deinit();
|
||||||
|
const fi = (try ri.next()).?;
|
||||||
|
const rec = try fi.to(struct { kind: enum { alpha, beta } }, .{});
|
||||||
|
try std.testing.expectEqual(.beta, rec.kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "coerce: string in a numeric field errors when strings_to_numbers is off" {
|
||||||
|
// The `close_price::200.00` shape that took down a downstream
|
||||||
|
// consumer: string separator on a numeric field.
|
||||||
|
const data =
|
||||||
|
\\#!srfv1
|
||||||
|
\\cost::200.00
|
||||||
|
;
|
||||||
|
var reader = std.Io.Reader.fixed(data);
|
||||||
|
var ri = try iterator(&reader, std.testing.allocator, .{});
|
||||||
|
defer ri.deinit();
|
||||||
|
const fi = (try ri.next()).?;
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.FloatNotNumberType,
|
||||||
|
fi.to(struct { cost: f64 }, .{}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue