Crunch supports a variety of field types for message definition. Each field type can have validators attached.
Scalars
Integer and floating-point types with optional validators.
struct ScalarExample {
CRUNCH_MESSAGE_FIELDS(count, value, flag);
static constexpr MessageId message_id = 10;
Field<1, Required, Int32<Positive, Odd>> count;
Field<2, Optional, Float64<Around<3.0, 0.25>>> value;
Field<3, Required, Bool<True>> flag;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const ScalarExample&) const = default;
};
ScalarExample msg;
if (auto err = msg.count.set(-5); err) {
}
msg.count.set(7);
msg.value.set(3.1);
msg.flag.set(true);
Enums
Custom enums with value constraints.
enum class Status : int32_t { IDLE = 0, RUNNING = 1, ERROR = 2 };
struct EnumExample {
CRUNCH_MESSAGE_FIELDS(status);
static constexpr MessageId message_id = 11;
Field<1, Required, Enum<Status, OneOf<Status::IDLE, Status::RUNNING>>> status;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const EnumExample&) const = default;
};
EnumExample msg;
msg.status.set(Status::RUNNING);
if (auto err = msg.status.set(Status::ERROR); err) {
}
Strings
Fixed-capacity strings with optional validators.
struct StringExample {
CRUNCH_MESSAGE_FIELDS(name, code);
static constexpr MessageId message_id = 12;
Field<1, Required, String<64, None>> name;
Field<2, Optional, String<32, StringNotEquals<"INVALID">>> code;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const StringExample&) const = default;
};
StringExample msg;
msg.name.set("Alice");
msg.code.set("ABC123");
Submessages
Nested message types.
struct Inner {
CRUNCH_MESSAGE_FIELDS(x, y);
static constexpr MessageId message_id = 20;
Field<1, Required, Int32<None>> x;
Field<2, Required, Int32<None>> y;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const Inner&) const = default;
};
struct Outer {
CRUNCH_MESSAGE_FIELDS(point);
Field<1, Required, Inner> point;
constexpr auto Validate() const -> std::optional<Error> {
return std::nullopt; }
bool operator==(const Outer&) const = default;
};
Inner inner;
inner.x.set(10);
inner.y.set(20);
Outer msg;
msg.point.set(inner);
constexpr auto Validate(const Message &message) noexcept -> std::optional< Error >
Validates a message (field presence + message-level validation).
Definition: crunch.hpp:123
int32_t MessageId
Unique identifier for a message type.
Definition: crunch_types.hpp:25
Arrays
Fixed-capacity arrays with element and aggregate validators.
struct ArrayExample {
CRUNCH_MESSAGE_FIELDS(values, names);
static constexpr MessageId message_id = 13;
ArrayField<1, Int32<Positive>, 10, LengthAtLeast<2>> values;
ArrayField<2, String<32, None>, 5, None> names;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const ArrayExample&) const = default;
};
ArrayExample msg;
msg.values.add(1);
msg.values.add(2);
msg.values.add(3);
std::array<int32_t, 3> arr = {10, 20, 30};
msg.values.set(arr);
for (const auto& val : msg.values) {
}
Maps
Fixed-capacity maps supporting any key/value types.
Performance Note: Map keys are not hashed or sorted. This means:
- Key lookup is O(n) linear scan
- Equality comparison is O(n²)
- Insertion checks key uniqueness via O(n) scan
When using complex key types (submessages, arrays, nested maps), each key comparison requires full deep equality, which can be slow for large maps.
struct MapExample {
CRUNCH_MESSAGE_FIELDS(config, scores);
static constexpr MessageId message_id = 14;
MapField<1, String<32, None>, Int32<None>, 10> config;
MapField<2, Int32<None>, Float64<None>, 5> scores;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const MapExample&) const = default;
};
MapExample msg;
msg.config.insert("timeout", 30);
msg.config.insert("retries", 3);
if (auto* val = msg.config.find("timeout")) {
}
auto timeout = msg.config.at("timeout");
Complex Key Types
Unlike most serialization formats, Crunch supports submessages, arrays, and even nested maps as map keys.
Warning: Complex key types have significant performance implications. Each insertion requires an O(n) scan comparing all existing keys, and each key comparison requires deep equality checking of the entire key structure.
struct Coordinate {
CRUNCH_MESSAGE_FIELDS(x, y);
static constexpr MessageId message_id = 15;
Field<1, Required, Int32<None>> x;
Field<2, Required, Int32<None>> y;
constexpr auto Validate() const -> std::optional<Error> { return std::nullopt; }
bool operator==(const Coordinate&) const = default;
};
struct GridData {
CRUNCH_MESSAGE_FIELDS(cells);
MapField<1, Coordinate, String<64, None>, 100> cells;
constexpr auto Validate() const -> std::optional<Error> {
return std::nullopt; }
bool operator==(const GridData&) const = default;
};
GridData grid;
Coordinate origin;
origin.x.set(0);
origin.y.set(0);
grid.cells.insert(origin, "start");
Coordinate target;
target.x.set(10);
target.y.set(5);
grid.cells.insert(target, "goal");
Cross-Field Validation
The Validate() method enables custom logic across fields.
struct OrderMessage {
CRUNCH_MESSAGE_FIELDS(quantity, unit_price, total);
static constexpr MessageId message_id = 30;
Field<1, Required, Int32<Positive>> quantity;
Field<2, Required, Float64<Positive>> unit_price;
Field<3, Required, Float64<Positive>> total;
constexpr auto Validate() const -> std::optional<Error> {
auto expected = *quantity.get() * *unit_price.get();
if (std::abs(*total.get() - expected) > 0.01) {
return Error::validation(3, "total does not match quantity * unit_price");
}
return std::nullopt;
}
bool operator==(const OrderMessage&) const = default;
};