Crunch
A Message Definition Language for Getting Things Right
Loading...
Searching...
No Matches
crunch_field.hpp
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <concepts>
6#include <crunch/core/crunch_types.hpp>
7#include <crunch/fields/crunch_scalar.hpp>
8#include <crunch/fields/crunch_string.hpp>
9#include <crunch/validators/crunch_validators.hpp>
10#include <cstdint>
11#include <optional>
12#include <span>
13#include <utility>
14
15namespace Crunch::serdes {
16template <std::size_t Alignment>
17struct StaticLayout;
18struct TlvLayout;
19} // namespace Crunch::serdes
20
21namespace Crunch::messages {
22
26template <typename T>
27concept IsMessage = requires {
28 { T::message_id };
29};
30
34template <typename T>
35concept IsPresenceValidator = requires(bool set, FieldId id) {
36 { T::check_presence(set, id) } -> std::same_as<std::optional<Error>>;
37};
38
48template <FieldId Id, typename PresenceValidator, typename Type>
50class Field {
51 public:
52 static_assert(Id <= MaxFieldId, "FieldId must be <= MaxFieldId (2^29 - 1)");
53 static constexpr FieldId field_id = Id;
54 using FieldType = Type;
55
56 constexpr Field() noexcept = default;
57
63 [[nodiscard]] constexpr auto validate_presence() const noexcept
64 -> std::optional<Error> {
65 return PresenceValidator::check_presence(set_, Id);
66 }
67
75 template <typename T>
76 [[nodiscard]] constexpr auto set(const T& val) noexcept {
77 if constexpr (requires {
78 {
79 value_.set(val)
80 } -> std::same_as<std::optional<Error>>;
81 }) {
82 auto err = value_.set(val);
83 if (!err) {
84 set_ = true;
85 }
86 return err;
87 } else if constexpr (requires { value_.set(val); }) {
88 value_.set(val);
89 set_ = true;
90 } else {
91 // Submessages do not have set(), so we assign.
92 value_ = val;
93 set_ = true;
94 }
95 }
96
100 template <typename T>
101 constexpr void set_without_validation(const T& val) noexcept
102 requires(!fields::is_string_v<Type> && !IsMessage<Type>)
103 {
104 value_.set_without_validation(val);
105 set_ = true;
106 }
107
117 [[nodiscard]] constexpr auto get() const noexcept {
118 if constexpr (IsMessage<Type>) {
119 return get_message_impl();
120 } else if constexpr (fields::is_scalar_v<Type>) {
121 return get_scalar_impl();
122 } else if constexpr (fields::is_string_v<Type>) {
123 return get_string_impl();
124 } else {
125 return std::nullopt;
126 }
127 }
128
132 constexpr void clear() noexcept {
133 set_ = false;
134 value_ = {};
135 }
136
142 [[nodiscard]] constexpr auto Validate() const noexcept
143 -> std::optional<Error> {
144 if (!set_) {
145 return std::nullopt;
146 }
147 return value_.Validate();
148 }
149
150 [[nodiscard]] constexpr bool operator==(const Field& other) const noexcept {
151 if (set_ != other.set_) {
152 return false;
153 }
154 if (!set_) {
155 return true;
156 }
157 return value_ == other.value_;
158 }
159
160 private:
161 [[nodiscard]] constexpr auto get_message_impl() const noexcept {
162 return set_ ? &value_ : nullptr;
163 }
164
165 [[nodiscard]] constexpr auto get_scalar_impl() const noexcept {
166 using V = typename Type::ValueType;
167 auto ret = std::optional<V>{};
168 if (set_) {
169 ret.emplace(value_.get());
170 }
171 return ret;
172 }
173
174 [[nodiscard]] constexpr auto get_string_impl() const noexcept {
175 auto ret = std::optional<std::string_view>{};
176 if (set_) {
177 ret.emplace(value_.get());
178 }
179 return ret;
180 }
181
182 Type value_{};
183 bool set_{false};
184
192 template <std::size_t Alignment>
194 friend struct Crunch::serdes::TlvLayout;
195};
196
197template <typename T>
198struct is_field : std::false_type {};
199
200template <FieldId Id, typename P, typename T>
201struct is_field<Field<Id, P, T>> : std::true_type {};
202
203template <typename T>
204inline constexpr bool is_field_v = is_field<T>::value;
205
209template <typename T>
210concept HasValidateWithId = requires(const T& t, FieldId id) {
211 { t.Validate(id) } -> std::same_as<std::optional<Error>>;
212};
213
217template <typename T>
218concept HasValidateNoId = requires(const T& t) {
219 { t.Validate() } -> std::same_as<std::optional<Error>>;
220};
221
225template <typename T>
226concept HasCrunchMessageInterface = requires {
227 { std::integral_constant<MessageId, T::message_id>{} };
228 { T{}.get_fields() };
229};
230
234template <FieldId Id, typename ElementType, std::size_t MaxSize,
235 typename... Validators>
236class ArrayField;
237
238template <FieldId Id, typename KeyField, typename ValueField,
239 std::size_t MaxSize, typename... Validators>
240class MapField;
241
242// Traits defined early for use in Concepts
243template <typename T>
244struct is_array_field : std::false_type {};
245
246template <FieldId Id, typename E, std::size_t M, typename... V>
247struct is_array_field<ArrayField<Id, E, M, V...>> : std::true_type {};
248
249template <typename T>
250inline constexpr bool is_array_field_v = is_array_field<T>::value;
251
252template <typename T>
253struct is_map_field : std::false_type {};
254
255template <FieldId Id, typename K, typename V, std::size_t M, typename... Vs>
256struct is_map_field<MapField<Id, K, V, M, Vs...>> : std::true_type {};
257
258template <typename T>
259inline constexpr bool is_map_field_v = is_map_field<T>::value;
260
265template <typename T>
267 Crunch::fields::is_scalar_v<std::remove_cvref_t<T>> ||
268 Crunch::fields::is_string_v<std::remove_cvref_t<T>> ||
270 is_array_field_v<std::remove_cvref_t<T>> ||
271 is_map_field_v<std::remove_cvref_t<T>>;
272
285template <FieldId Id, typename ElementType, std::size_t MaxSize,
286 typename... Validators>
288 static_assert(ValidElementType<ElementType>,
289 "Invalid ElementType for ArrayField");
290 static_assert(sizeof...(Validators) >= 1,
291 "ArrayField requires at least one validator");
292 static_assert(Id <= MaxFieldId, "FieldId must be <= MaxFieldId (2^29 - 1)");
293
294 public:
295 static constexpr FieldId field_id = Id;
296 // cppcheck-suppress unusedStructMember
297 static constexpr std::size_t max_size = MaxSize;
298 using ValueType = ElementType;
299 using FieldType = ArrayField;
300
301 constexpr ArrayField() noexcept = default;
302
308 constexpr std::optional<Error> add(const ElementType& val) noexcept {
309 if (current_len_ >= MaxSize) {
310 return Error::capacity_exceeded(Id, "array capacity exceeded");
311 }
312 items_[current_len_++] = val;
313 return std::nullopt;
314 }
315
321 template <std::size_t N>
322 requires(N <= MaxSize)
323 constexpr std::optional<Error> set(
324 const std::array<ElementType, N>& other) noexcept {
325 current_len_ = N;
326 std::copy(other.begin(), other.end(), items_.begin());
327 return std::nullopt;
328 }
329
334 constexpr std::optional<Error> set(const ArrayField& other) noexcept {
335 *this = other;
336 return std::nullopt;
337 }
338
342 constexpr void clear() noexcept { current_len_ = 0; }
343
348 [[nodiscard]] constexpr std::size_t size() const noexcept {
349 return current_len_;
350 }
351
356 [[nodiscard]] constexpr bool empty() const noexcept {
357 return current_len_ == 0;
358 }
359
364 [[nodiscard]] constexpr auto get() const noexcept {
365 using SpanT = std::span<const ElementType>;
366 return SpanT{items_.data(), current_len_};
367 }
368
373 constexpr const ElementType& operator[](std::size_t index) const noexcept {
374 return items_[index];
375 }
376
381 constexpr const ElementType& at(std::size_t index) const {
382 return items_[index];
383 }
384
389 [[nodiscard]] constexpr auto Validate() const noexcept
390 -> std::optional<Error> {
391 // Validate each element
392 for (std::size_t i = 0; i < current_len_; ++i) {
393 std::optional<Error> err;
394 if constexpr (HasValidateWithId<ElementType>) {
395 err = items_[i].Validate(Id);
396 } else if constexpr (HasValidateNoId<ElementType>) {
397 err = items_[i].Validate();
398 }
399 if (err.has_value()) {
400 return err;
401 }
402 }
403
404 // Run array-level validators
405 if constexpr (sizeof...(Validators) > 0) {
406 for (const auto& result : {Validators::Check(*this, Id)...}) {
407 if (result.has_value()) {
408 return result;
409 }
410 }
411 }
412
413 return std::nullopt;
414 }
415
416 [[nodiscard]] constexpr bool operator==(
417 const ArrayField& other) const noexcept {
418 if (current_len_ != other.current_len_) {
419 return false;
420 }
421 return std::equal(items_.begin(), items_.begin() + current_len_,
422 other.items_.begin());
423 }
424
425 // STL iterator support
426 auto begin() const noexcept { return items_.begin(); }
427 auto end() const noexcept { return items_.begin() + current_len_; }
428 auto begin() noexcept { return items_.begin(); }
429 auto end() noexcept { return items_.begin() + current_len_; }
430
431 private:
432 std::array<ElementType, MaxSize> items_{};
433 std::size_t current_len_{0};
434
435 template <std::size_t Alignment>
436 friend struct Crunch::serdes::StaticLayout;
437 friend struct Crunch::serdes::TlvLayout;
438};
439
440// Helper to extract ValueType for Scalar/String, or use T itself for others
441template <typename T>
443 using type = T;
444};
445
446template <typename T>
447 requires Crunch::fields::is_scalar_v<T> || Crunch::fields::is_string_v<T>
449 using type = typename T::ValueType;
450};
451
452template <typename T>
453using field_value_type_t = typename field_value_type<T>::type;
454
467template <FieldId Id, typename KeyField, typename ValueField,
468 std::size_t MaxSize, typename... Validators>
469class MapField {
470 static_assert(ValidElementType<KeyField>, "Invalid KeyField type");
471 static_assert(ValidElementType<ValueField>, "Invalid ValueField type");
472 static_assert(Id <= MaxFieldId, "FieldId must be <= MaxFieldId (2^29 - 1)");
473
474 public:
475 static constexpr FieldId field_id = Id;
476 // cppcheck-suppress unusedStructMember
477 static constexpr std::size_t max_size = MaxSize;
478
479 using KeyType = field_value_type_t<KeyField>;
480 using ValueType = field_value_type_t<ValueField>;
481 using PairType = std::pair<KeyField, ValueField>;
482 using FieldType = MapField;
483
484 constexpr MapField() noexcept = default;
485
493 constexpr std::optional<Error> insert(const KeyType& key,
494 const ValueType& value) noexcept {
495 std::optional<Error> err;
496
497 // Validate Key
498 if constexpr (Crunch::fields::is_scalar_v<KeyField> ||
499 Crunch::fields::is_string_v<KeyField>) {
500 // For scalar/string, KeyType is value type. Validate against
501 // validator.
502 err = KeyField::Validate(key, 0);
503 } else {
504 // For complex, KeyType is field type. Validate against internal
505 // rules.
506 err = key.Validate();
507 }
508 if (err) {
509 return err;
510 }
511
512 // Validate Value
513 if constexpr (Crunch::fields::is_scalar_v<ValueField> ||
514 Crunch::fields::is_string_v<ValueField>) {
515 err = ValueField::Validate(value, 0);
516 } else {
517 err = value.Validate();
518 }
519 if (err) {
520 return err;
521 }
522
523 if (current_len_ >= MaxSize) {
524 return Error::capacity_exceeded(Id, "map capacity exceeded");
525 }
526
527 // Check for duplicate key
528 if (at(key).has_value()) {
529 return Error::validation(Id, "Duplicate key in map");
530 }
531
532 auto& pair = items_[current_len_];
533
534 // 3. Store Key
535 if constexpr (Crunch::fields::is_scalar_v<KeyField>) {
536 pair.first.set_without_validation(key);
537 } else if constexpr (Crunch::fields::is_string_v<KeyField>) {
538 static_cast<void>(pair.first.set(key));
539 } else {
540 pair.first = key;
541 }
542
543 // 4. Store Value
544 if constexpr (Crunch::fields::is_scalar_v<ValueField>) {
545 pair.second.set_without_validation(value);
546 } else if constexpr (Crunch::fields::is_string_v<ValueField>) {
547 static_cast<void>(pair.second.set(value));
548 } else {
549 pair.second = value;
550 }
551
552 current_len_++;
553 return std::nullopt;
554 }
555
556 // Overload for inserting std::pair
557 constexpr std::optional<Error> insert(
558 const std::pair<KeyType, ValueType>& p) noexcept {
559 return insert(p.first, p.second);
560 }
561
567 // cppcheck-suppress unusedFunction
568 constexpr bool remove(const KeyType& key) noexcept {
569 for (std::size_t i = 0; i < current_len_; ++i) {
570 const auto& stored_key_field = items_[i].first;
571 if (key_equals(stored_key_field, key)) {
572 // Found key at index i.
573 // Clear element at i
574 items_[i].first.clear();
575 items_[i].second.clear();
576
577 // Shift remaining elements down
578 for (std::size_t j = i; j < current_len_ - 1; ++j) {
579 items_[j] = items_[j + 1];
580 }
581 // Clear last element (now duplicated at penultimate pos)
582 // Actually the move assignment above likely did a copy.
583 // We should clear the old last element slot to be safe/clean.
584 items_[current_len_ - 1].first.clear();
585 items_[current_len_ - 1].second.clear();
586
587 current_len_--;
588 return true;
589 }
590 }
591 return false;
592 }
593
599 constexpr std::optional<ValueField*> at(const KeyType& key) noexcept {
600 for (std::size_t i = 0; i < current_len_; ++i) {
601 // We need to extract the underlying value from the KeyField to
602 // compare
603 const auto& stored_key_field = items_[i].first;
604 // Assuming scalar/string keys have .get() returning optional or
605 // value
606 if (key_equals(stored_key_field, key)) {
607 return &items_[i].second;
608 }
609 }
610 return std::nullopt;
611 }
612
616 [[nodiscard]] constexpr std::size_t size() const noexcept {
617 return current_len_;
618 }
619
623 [[nodiscard]] constexpr bool empty() const noexcept {
624 return current_len_ == 0;
625 }
626
630 constexpr void clear() noexcept { current_len_ = 0; }
631
635 [[nodiscard]] constexpr auto Validate() const noexcept
636 -> std::optional<Error> {
637 // Validate each pair
638 for (std::size_t i = 0; i < current_len_; ++i) {
639 if (const auto err = items_[i].first.Validate(); err) {
640 return err;
641 }
642 if (const auto err = items_[i].second.Validate(); err) {
643 return err;
644 }
645 }
646
647 // Run map-level validators
648 for (const auto& result : {Validators::Check(*this, Id)...}) {
649 if (result.has_value()) {
650 return result;
651 }
652 }
653
654 return std::nullopt;
655 }
656
662 [[nodiscard]] constexpr bool operator==(
663 const MapField& other) const noexcept {
664 if (current_len_ != other.current_len_) {
665 return false;
666 }
667
668 // I don't have a faster way to do this given
669 // the requirement that order doesn't matter.
670 // TODO: Enforce ordering? Figure out a comparison
671 // operator for Arrays/Maps/Strings?
672 for (std::size_t i = 0; i < current_len_; ++i) {
673 const auto& my_key = items_[i].first;
674 const auto& my_val = items_[i].second;
675
676 if (!other.has_entry(my_key, my_val)) {
677 return false;
678 }
679 }
680 return true;
681 }
682
683 auto begin() const noexcept { return items_.begin(); }
684 auto end() const noexcept { return items_.begin() + current_len_; }
685 auto begin() noexcept { return items_.begin(); }
686 auto end() noexcept { return items_.begin() + current_len_; }
687
688 private:
689 std::array<PairType, MaxSize> items_{};
690 std::size_t current_len_{0};
691
692 constexpr bool has_entry(const KeyField& key, const ValueField& val) const {
693 auto it = std::find_if(
694 begin(), end(), [&](const PairType& p) { return p.first == key; });
695 return it != end() && it->second == val;
696 }
697
698 static constexpr bool key_equals(const KeyField& stored,
699 const KeyType& key) {
700 if constexpr (Crunch::fields::is_scalar_v<KeyField> ||
701 Crunch::fields::is_string_v<KeyField>) {
702 return stored.get() == key;
703 } else {
704 return stored == key;
705 }
706 }
707
708 template <std::size_t Alignment>
709 friend struct Crunch::serdes::StaticLayout;
710 friend struct Crunch::serdes::TlvLayout;
711};
712
713} // namespace Crunch::messages
Concept for valid element types within Field or ArrayField.
Definition: crunch_field.hpp:287
constexpr std::optional< Error > set(const ArrayField &other) noexcept
Set array contents from another ArrayField.
Definition: crunch_field.hpp:334
constexpr std::optional< Error > add(const ElementType &val) noexcept
Adds an element to the array.
Definition: crunch_field.hpp:308
constexpr auto Validate() const noexcept -> std::optional< Error >
Validates the array and its elements.
Definition: crunch_field.hpp:389
constexpr bool empty() const noexcept
Check if the array is empty.
Definition: crunch_field.hpp:356
constexpr const ElementType & at(std::size_t index) const
Access element at index.
Definition: crunch_field.hpp:381
constexpr std::optional< Error > set(const std::array< ElementType, N > &other) noexcept
Set array contents from a std::array.
Definition: crunch_field.hpp:323
constexpr auto get() const noexcept
Get read-only span of active elements.
Definition: crunch_field.hpp:364
constexpr std::size_t size() const noexcept
Get the current number of elements.
Definition: crunch_field.hpp:348
constexpr const ElementType & operator[](std::size_t index) const noexcept
Access element at index (unchecked).
Definition: crunch_field.hpp:373
constexpr void clear() noexcept
Clear the array (sets size to 0).
Definition: crunch_field.hpp:342
Wrapper for a message field within a CrunchMessage.
Definition: crunch_field.hpp:50
constexpr auto set(const T &val) noexcept
Set the value.
Definition: crunch_field.hpp:76
constexpr void clear() noexcept
Clear the field value.
Definition: crunch_field.hpp:132
constexpr auto validate_presence() const noexcept -> std::optional< Error >
Check presence requirement.
Definition: crunch_field.hpp:63
constexpr void set_without_validation(const T &val) noexcept
Set the value, bypassing validation.
Definition: crunch_field.hpp:101
constexpr auto Validate() const noexcept -> std::optional< Error >
Validate the field value.
Definition: crunch_field.hpp:142
constexpr auto get() const noexcept
Get the user-facing value.
Definition: crunch_field.hpp:117
Map field mapping keys to values.
Definition: crunch_field.hpp:469
constexpr bool remove(const KeyType &key) noexcept
Removes a key and its value from the map.
Definition: crunch_field.hpp:568
constexpr std::optional< Error > insert(const KeyType &key, const ValueType &value) noexcept
Inserts a key-value pair into the map.
Definition: crunch_field.hpp:493
constexpr auto Validate() const noexcept -> std::optional< Error >
Validate the map and its elements.
Definition: crunch_field.hpp:635
constexpr bool empty() const noexcept
Check if the map is empty.
Definition: crunch_field.hpp:623
constexpr void clear() noexcept
Clear the map.
Definition: crunch_field.hpp:630
constexpr std::size_t size() const noexcept
Get the current number of elements.
Definition: crunch_field.hpp:616
constexpr bool operator==(const MapField &other) const noexcept
Checks if two maps are equal (set equality).
Definition: crunch_field.hpp:662
constexpr std::optional< ValueField * > at(const KeyType &key) noexcept
Get reference to value by key.
Definition: crunch_field.hpp:599
Concept to detect if a type quacks like a Crunch Message.
Definition: crunch_field.hpp:226
Concept to detect if a type has a parameterless Validate() method.
Definition: crunch_field.hpp:218
Concept to detect if a type has a Validate(FieldId) method.
Definition: crunch_field.hpp:210
Concept to identify a message type.
Definition: crunch_field.hpp:27
Concept for a Presence Validator.
Definition: crunch_field.hpp:35
Concept for valid element type in Array/Map. Must be a scalar, string, message, array,...
Definition: crunch_field.hpp:266
int32_t FieldId
Unique identifier for a field within a Crunch message.
Definition: crunch_types.hpp:11
Represents an error occurred during Crunch operations.
Definition: crunch_types.hpp:75
static constexpr Error validation(FieldId id, const char(&msg)[N]) noexcept
Creates an error representing a validation failure.
Definition: crunch_types.hpp:97
static constexpr Error capacity_exceeded(FieldId id, const char(&msg)[N]) noexcept
Creates an error representing capacity exceeded. Used for strings and aggregated fields.
Definition: crunch_types.hpp:133
Definition: crunch_field.hpp:442
Definition: crunch_field.hpp:244
Definition: crunch_field.hpp:198
Definition: crunch_field.hpp:253
A deterministic, fixed-size binary serialization policy.
Definition: crunch_static_layout.hpp:24
Definition: crunch_tlv_layout.hpp:52