Non-exhaustive enums don’t work with EnumSet. It’s too big of a trade-off in my opinion. Given that you’ll likely need to perform other types of validation on the data anyway, it’s probably better to code a mechanism that uses std.meta.intToEnum
to check whether the value would lead to an error.
Here’s an example that I whipped up:
const std = @import("std");
const Pet = enum { Cat, Dog, Camel };
const Struct = packed struct {
pet: Pet,
number: u17,
};
fn validateStructBytes(comptime T: type, bytes: []const u8, comptime validators: ?type) bool {
const info = @typeInfo(T).Struct;
const NoEnum = @Type(.{
.Struct = .{
.layout = info.layout,
.backing_integer = info.backing_integer,
.is_tuple = info.is_tuple,
.decls = &.{},
.fields = create: {
comptime var fields: [info.fields.len]std.builtin.Type.StructField = undefined;
inline for (info.fields, 0..) |field, index| {
fields[index] = .{
.name = field.name,
.default_value = null,
.is_comptime = field.is_comptime,
.alignment = field.alignment,
.type = switch (@typeInfo(field.type)) {
.Enum => |em| em.tag_type,
else => field.type,
},
};
}
break :create &fields;
},
},
});
if (bytes.len != @sizeOf(T)) {
return false;
}
const ptr = std.mem.bytesAsValue(NoEnum, bytes[0..@sizeOf(T)]);
inline for (info.fields) |field| {
const raw_value = @field(ptr, field.name);
const conversion: anyerror!field.type = switch (@typeInfo(field.type)) {
.Enum => std.meta.intToEnum(field.type, raw_value),
else => raw_value,
};
if (conversion) |value| {
if (validators) |ns| {
if (@hasDecl(ns, field.name)) {
const callback = @field(ns, field.name);
if (!callback(value)) {
return false;
}
}
}
} else |_| {
return false;
}
}
return true;
}
test "validateStructBytes" {
std.debug.print("\n", .{});
const test_struct: Struct = .{
.pet = .Dog,
.number = 23,
};
const bytes1 = std.mem.asBytes(&test_struct);
const bytes2: []const u8 = &.{ 0xFF, 0xFF, 0xFF, 0xFF };
const result1 = validateStructBytes(Struct, bytes1, null);
std.debug.print("{s}\n", .{if (result1) "valid" else "invalid"});
const result2 = validateStructBytes(Struct, bytes2, null);
std.debug.print("{s}\n", .{if (result2) "valid" else "invalid"});
const validators = struct {
pub fn number(value: u17) bool {
return value < 20;
}
};
const result3 = validateStructBytes(Struct, bytes1, validators);
std.debug.print("{s}\n", .{if (result3) "valid" else "invalid"});
}
The function basically creates a parallel struct type where enums are replaced with ints so that we can perform the check. It accepts an optional namespace for performing additional validation.