parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
struct.h
Go to the documentation of this file.
1#pragma once
2
15#include <parcel/cell.h>
16#include <parcel/defaults.h>
17#include <parcel/descriptor.h>
18#include <parcel/list.h>
19#include <parcel/map.h>
20#include <parcel/primitive.h>
21#include <parcel/registry.h>
22
23#include <algorithm>
24#include <map>
25#include <memory>
26#include <optional>
27#include <stdexcept>
28#include <string>
29#include <string_view>
30#include <type_traits>
31#include <utility>
32#include <vector>
33
34namespace parcel {
35class ParcelRegistry;
36
37namespace detail {
38
39template <typename T>
40struct member_pointer_traits;
41
42template <typename C, typename F>
43struct member_pointer_traits<F C::*> {
44 using class_type = C;
45 using field_type = F;
46};
47
48template <typename T>
49struct is_optional : std::false_type {};
50template <typename U>
51struct is_optional<std::optional<U>> : std::true_type {};
52
53template <typename T>
54inline constexpr bool is_optional_v = is_optional<T>::value;
55
56} // namespace detail
57
66template <typename Payload>
73 virtual void to_json_into(json_t& v_obj, Payload const& p) const = 0;
74
81 virtual void
82 from_json_into(json_t const& field_json, Payload& p, ParcelRegistry const& reg) const = 0;
83
89 virtual void to_string_into(std::string& out, Payload const& p) const = 0;
90
98 [[nodiscard]] virtual std::partial_ordering compare(Payload const& a,
99 Payload const& b) const = 0;
100
103
105 virtual void set_required(bool r) = 0;
106};
107
124template <typename Payload, typename FieldT, CellLike CellT>
125class MemberFieldDescriptor final : public IPayloadFieldDescriptor<Payload> {
126 // clang-format-22 minor versions disagree on Payload::* spacing.
127 // clang-format off
128 FieldT Payload::*member_ptr_;
129 // clang-format on
130 std::string key_;
131 DisplayInfo display_info_;
132 bool required_ = !detail::is_optional_v<FieldT>;
133
134public:
141 // clang-format off
142 MemberFieldDescriptor(FieldT Payload::*member_ptr, std::string key, DisplayInfo info)
143 // clang-format on
144 : member_ptr_(member_ptr), key_(std::move(key)), display_info_(std::move(info)) {}
145
146 [[nodiscard]] std::string_view key() const override {
147 return key_;
148 }
149 [[nodiscard]] std::string_view kind() const override {
150 return CellT::kind_id;
151 }
152 [[nodiscard]] DisplayInfo display_info() const override {
153 return display_info_;
154 }
155 [[nodiscard]] bool is_required() const override {
156 return required_;
157 }
158
159 [[nodiscard]] json_t to_json() const override {
160 return json_t{
161 {"key", key_},
162 {"kind", CellT::kind_id},
163 {"display_info", json_t(display_info_)},
164 {"required", required_},
165 };
166 }
167
169 return display_info_;
170 }
171 void set_required(const bool r) override {
172 required_ = r;
173 }
174
175 void to_json_into(json_t& v_obj, Payload const& p) const override {
176 if constexpr (detail::is_optional_v<FieldT>) {
177 if (!(p.*member_ptr_).has_value()) {
178 return;
179 }
180 CellT wrapper(*(p.*member_ptr_));
181 v_obj[key_] = wrapper.to_json();
182 } else {
183 CellT wrapper(p.*member_ptr_);
184 v_obj[key_] = wrapper.to_json();
185 }
186 }
187
188 void
189 from_json_into(json_t const& field_json, Payload& p, ParcelRegistry const& reg) const override {
190 const cell_t v = CellT::from_json(field_json, reg);
191 auto* typed = dynamic_cast<CellT*>(v.get());
192 if (typed == nullptr) {
193 throw TypeException(
194 "StructCell: field '" + key_ + "' from_json did not return CellT", {}, key_);
195 }
196 p.*member_ptr_ = std::move(typed->value);
197 }
198
199 // Overrides IPayloadFieldDescriptor::compare(a, b); signature is fixed.
200 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
201 [[nodiscard]] std::partial_ordering compare(Payload const& a, Payload const& b) const override {
202 if constexpr (detail::is_optional_v<FieldT>) {
203 const auto& oa = a.*member_ptr_;
204 const auto& ob = b.*member_ptr_;
205 if (!oa.has_value() && !ob.has_value()) {
206 return std::partial_ordering::equivalent;
207 }
208 if (!oa.has_value()) {
209 return std::partial_ordering::less;
210 }
211 if (!ob.has_value()) {
212 return std::partial_ordering::greater;
213 }
214 CellT wa(*oa);
215 CellT wb(*ob);
216 return wa.compare(wb);
217 } else {
218 CellT wa(a.*member_ptr_);
219 CellT wb(b.*member_ptr_);
220 return wa.compare(wb);
221 }
222 }
223
224 void to_string_into(std::string& out, Payload const& p) const override {
225 out += key_;
226 out += ": ";
227 if constexpr (detail::is_optional_v<FieldT>) {
228 if (!(p.*member_ptr_).has_value()) {
229 out += "<nullopt>";
230 return;
231 }
232 CellT wrapper(*(p.*member_ptr_));
233 out += wrapper.to_string();
234 } else {
235 CellT wrapper(p.*member_ptr_);
236 out += wrapper.to_string();
237 }
238 }
239};
240
253template <typename DerivedPayload, typename ParentPayload>
254class InheritedFieldDescriptor final : public IPayloadFieldDescriptor<DerivedPayload> {
255 static_assert(std::is_base_of_v<ParentPayload, DerivedPayload>,
256 "InheritedFieldDescriptor: DerivedPayload must derive from ParentPayload");
257
258 std::shared_ptr<IPayloadFieldDescriptor<ParentPayload>> inner_;
259
260public:
266 : inner_(std::move(inner)) {}
267
268 [[nodiscard]] std::string_view key() const override {
269 return inner_->key();
270 }
271 [[nodiscard]] std::string_view kind() const override {
272 return inner_->kind();
273 }
274 [[nodiscard]] DisplayInfo display_info() const override {
275 return inner_->display_info();
276 }
277 [[nodiscard]] bool is_required() const override {
278 return inner_->is_required();
279 }
280 [[nodiscard]] json_t to_json() const override {
281 return inner_->to_json();
282 }
283
285 return inner_->mutable_display_info();
286 }
287 void set_required(const bool r) override {
288 inner_->set_required(r);
289 }
290
291 void to_json_into(json_t& v_obj, DerivedPayload const& p) const override {
292 inner_->to_json_into(v_obj, static_cast<ParentPayload const&>(p));
293 }
294 void from_json_into(json_t const& field_json,
295 DerivedPayload& p,
296 ParcelRegistry const& reg) const override {
297 inner_->from_json_into(field_json, static_cast<ParentPayload&>(p), reg);
298 }
299 void to_string_into(std::string& out, DerivedPayload const& p) const override {
300 inner_->to_string_into(out, static_cast<ParentPayload const&>(p));
301 }
302 [[nodiscard]] std::partial_ordering compare(DerivedPayload const& a,
303 DerivedPayload const& b) const override {
304 return inner_->compare(static_cast<ParentPayload const&>(a),
305 static_cast<ParentPayload const&>(b));
306 }
307};
308
341template <typename Payload>
343public:
344 using payload_field_t = std::shared_ptr<IPayloadFieldDescriptor<Payload>>;
345 using payload_field_descriptors_t = std::vector<payload_field_t>;
346
354 template <auto MemberPtr, CellLike CellT>
355 FieldsBuilder& field(std::string key) {
356 using mp_t = decltype(MemberPtr);
357 using traits = detail::member_pointer_traits<mp_t>;
358 static_assert(std::is_base_of_v<typename traits::class_type, Payload>,
359 "FieldsBuilder: member pointer's class must be Payload "
360 "or a base class of Payload");
361
362 using FieldT = typename traits::field_type;
363 // Implicit base-to-derived member-pointer conversion happens here at
364 // runtime — the conversion isn't constexpr, so the pointer is held
365 // by the descriptor instead of a non-type template parameter.
366 // clang-format off
367 FieldT Payload::*mp = MemberPtr;
368 // clang-format on
369 auto fd = std::make_shared<MemberFieldDescriptor<Payload, FieldT, CellT>>(
370 mp, std::move(key), DisplayInfo{});
371 replace_or_append(std::move(fd));
372 return *this;
373 }
374
387 template <auto MemberPtr>
388 FieldsBuilder& field(std::string key)
389 requires HasDefaultCellWrapper<
390 typename detail::member_pointer_traits<decltype(MemberPtr)>::field_type>
391 {
392 using FieldT = typename detail::member_pointer_traits<decltype(MemberPtr)>::field_type;
393 return field<MemberPtr, default_cell_for_t<FieldT>>(std::move(key));
394 }
395
402 FieldsBuilder& name(std::string v) {
403 require_last("name")->mutable_display_info().name = std::move(v);
404 return *this;
405 }
406
413 FieldsBuilder& description(std::string v) {
414 require_last("description")->mutable_display_info().description = std::move(v);
415 return *this;
416 }
417
425 FieldsBuilder& icon(comms::Icon icon) {
426 require_last("icon")->mutable_display_info().icon = icon;
427 return *this;
428 }
429
438 FieldsBuilder& icon(std::string const& v) {
439 return icon(comms::Icon::from(v));
440 }
441
449 FieldsBuilder& color(comms::Color color) {
450 require_last("color")->mutable_display_info().color = color;
451 return *this;
452 }
453
462 FieldsBuilder& color(std::string const& v) {
463 const auto parsed = comms::Color::parse(v);
464 if (!parsed) {
465 throw InvalidJsonException("FieldsBuilder::color: '" + v + "' is not a valid color");
466 }
467 return color(*parsed);
468 }
469
476 FieldsBuilder& is_required(bool v = true) {
477 require_last("is_required")->set_required(v);
478 return *this;
479 }
480
487 return is_required(false);
488 }
489
509 template <CellLike ParentCell>
510 requires std::is_base_of_v<typename ParentCell::payload_type, Payload>
512 using ParentPayload = typename ParentCell::payload_type;
513 for (auto parent_fields = ParentCell::field_descriptors();
514 auto& parent_field : parent_fields) {
515 auto adapter = std::make_shared<InheritedFieldDescriptor<Payload, ParentPayload>>(
516 std::move(parent_field));
517 replace_or_append(std::move(adapter));
518 }
519 return *this;
520 }
521
533 FieldsBuilder& remove_field(const std::string_view key) {
534 std::erase_if(fields_, [&](payload_field_t const& f) { return f->key() == key; });
535 last_ = nullptr;
536 return *this;
537 }
538
543 payload_field_descriptors_t build() {
544 last_ = nullptr;
545 return std::move(fields_);
546 }
547
548private:
549 payload_field_descriptors_t fields_;
550 IPayloadFieldDescriptor<Payload>* last_ = nullptr;
551
553 require_last(const std::string_view modifier) const {
554 if (last_ == nullptr) {
555 throw std::runtime_error("FieldsBuilder::" + std::string(modifier) +
556 " called before any .field<>()");
557 }
558 return last_;
559 }
560
561 void replace_or_append(payload_field_t fd) {
562 const auto key = fd->key();
563 for (auto& existing : fields_) {
564 if (existing->key() == key) {
565 existing = fd;
566 last_ = fd.get();
567 return;
568 }
569 }
570 last_ = fd.get();
571 fields_.push_back(std::move(fd));
572 }
573};
574
584template <typename Derived, typename Payload>
586 public IHasFields,
587 public ISubTypes {
589
590public:
591 using payload_field_descriptors_t =
592 typename FieldsBuilder<Payload>::payload_field_descriptors_t;
593
599 StructCellTypeDescriptor(DisplayInfo info, payload_field_descriptors_t fields)
600 : base_t(std::move(info)), fields_(std::move(fields)) {}
601
602 [[nodiscard]] descriptor::CellCategory category() const override {
603 return descriptor::CellCategory::Struct;
604 }
605
606 [[nodiscard]] cell_t cell_from_json(json_t const& j, ParcelRegistry const& reg) const override {
607 return Derived::from_json(j, reg);
608 }
609
610 [[nodiscard]] json_t to_json() const override {
611 auto j = base_t::base_to_json();
612 json_t arr = json_t::array();
613 for (auto const& f : fields_) {
614 arr.push_back(f->to_json());
615 }
616 j["fields"] = std::move(arr);
617 return j;
618 }
619
620 [[nodiscard]] field_descriptors_t fields() const override {
622 for (auto const& f : fields_) {
623 out.emplace(std::string(f->key()), std::static_pointer_cast<IFieldDescriptor>(f));
624 }
625 return out;
626 }
627
628 [[nodiscard]] std::vector<std::string_view> sub_kinds() const override {
629 std::vector<std::string_view> out;
630 out.reserve(fields_.size());
631 for (auto const& f : fields_) {
632 out.push_back(f->kind());
633 }
634 return out;
635 }
636
638 [[nodiscard]] payload_field_descriptors_t const& payload_fields() const {
639 return fields_;
640 }
641
642private:
643 payload_field_descriptors_t fields_;
644};
645
684template <typename Derived, typename Payload, FixedString StructId>
685class StructCell : public BaseCell<Derived, Payload> {
687
688public:
689 using payload_type = Payload;
690
691 using base_t::base_t;
692 using base_t::operator=;
693
700 static constexpr bool allow_extra_fields = false;
701
703 static constexpr std::string_view struct_id = StructId.view();
704
712 static constexpr std::string_view kind_id = id_join_lit_v<"s:", StructId>;
713
722 return {};
723 }
724
726 std::map<std::string, cell_t> extras;
727
728 [[nodiscard]] std::string to_string() const override {
729 std::string out = "{";
730 bool first = true;
731 for (auto const& f : Derived::field_descriptors()) {
732 if (!first) {
733 out += ", ";
734 }
735 f->to_string_into(out, this->value);
736 first = false;
737 }
738 if constexpr (Derived::allow_extra_fields) {
739 for (auto const& [key, ev] : extras) {
740 if (!first) {
741 out += ", ";
742 }
743 out += key;
744 out += ": ";
745 out += ev ? ev->to_string() : "<null>";
746 first = false;
747 }
748 }
749 out += "}";
750 return out;
751 }
752
753 [[nodiscard]] json_t to_json() const override {
754 json_t v_obj = json_t::object();
755 for (auto const& f : Derived::field_descriptors()) {
756 f->to_json_into(v_obj, this->value);
757 }
758 if constexpr (Derived::allow_extra_fields) {
759 for (auto const& [key, ev] : extras) {
760 v_obj[key] = ev ? ev->to_json() : json_t();
761 }
762 }
763 json_t j{
764 {ICell::KEY_KIND, Derived::kind_id},
765 {ICell::KEY_VALUE, std::move(v_obj)},
766 };
767 this->inject_display_info(j);
768 return j;
769 }
770
771 [[nodiscard]] cell_t clone() const override {
772 auto copy = std::make_shared<Derived>(static_cast<Derived const&>(*this));
773 if constexpr (Derived::allow_extra_fields) {
774 copy->extras.clear();
775 for (auto const& [key, ev] : extras) {
776 copy->extras.emplace(key, ev ? ev->clone() : nullptr);
777 }
778 }
779 return copy;
780 }
781
798 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
799 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
800 return k_cmp;
801 }
802 const auto* o = dynamic_cast<Derived const*>(&other);
803 if (o == nullptr) {
804 return std::partial_ordering::unordered;
805 }
806 for (auto const& f : Derived::field_descriptors()) {
807 if (const auto c = f->compare(this->value, o->value); c != 0) {
808 return c;
809 }
810 }
811 if constexpr (Derived::allow_extra_fields) {
812 // std::map already iterates by key; lexicographic compare on
813 // (key, value-cell) pairs, with null-cell < non-null-cell.
814 auto ai = extras.begin();
815 auto bi = o->extras.begin();
816 while (ai != extras.end() && bi != o->extras.end()) {
817 if (const auto kc = ai->first <=> bi->first; kc != 0) {
818 return kc;
819 }
820 const bool an = ai->second == nullptr;
821 if (const bool bn = bi->second == nullptr; an != bn) {
822 return an ? std::partial_ordering::less : std::partial_ordering::greater;
823 }
824 if (!an) {
825 if (const auto vc = *ai->second <=> *bi->second; vc != 0) {
826 return vc;
827 }
828 }
829 ++ai;
830 ++bi;
831 }
832 return extras.size() <=> o->extras.size();
833 }
834 return std::partial_ordering::equivalent;
835 }
836
850 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
851 if (!j.is_object()) {
852 throw InvalidJsonException(std::string("Expected JSON object for StructCell '") +
853 std::string(Derived::kind_id) + "'",
854 std::string(Derived::kind_id));
855 }
856
857 const auto it_k = j.find(ICell::KEY_KIND);
858 if (it_k == j.end() || !it_k->is_string()) {
859 throw InvalidJsonException(std::string("StructCell '") + std::string(Derived::kind_id) +
860 "': missing/invalid 'k'",
861 std::string(Derived::kind_id));
862 }
863 if (it_k->get<std::string_view>() != Derived::kind_id) {
864 throw KindMismatchException(std::string("StructCell '") +
865 std::string(Derived::kind_id) +
866 "': missing/invalid 'k'",
867 std::string(Derived::kind_id));
868 }
869
870 const auto it_v = j.find(ICell::KEY_VALUE);
871 if (it_v == j.end() || !it_v->is_object()) {
872 throw InvalidJsonException(std::string("StructCell '") + std::string(Derived::kind_id) +
873 "': missing/invalid 'v' (expected object)",
874 std::string(Derived::kind_id));
875 }
876
877 auto out = std::make_shared<Derived>();
878 auto field_descs = Derived::field_descriptors();
879
880 std::map<std::string_view, IPayloadFieldDescriptor<Payload> const*> by_key;
881 for (auto const& f : field_descs) {
882 by_key.emplace(f->key(), f.get());
883 }
884
885 for (auto const& f : field_descs) {
886 const auto it = it_v->find(f->key());
887 if (it == it_v->end()) {
888 if (f->is_required()) {
890 std::string("StructCell '") + std::string(Derived::kind_id) +
891 "': missing required field '" + std::string(f->key()) + "'",
892 std::string(Derived::kind_id),
893 std::string(f->key()));
894 }
895 continue;
896 }
897 f->from_json_into(*it, out->value, reg);
898 }
899
900 for (auto const& [key, raw] : it_v->items()) {
901 if (by_key.contains(key)) {
902 continue;
903 }
904 if constexpr (Derived::allow_extra_fields) {
905 out->extras.emplace(key, reg.cell_from_json(raw));
906 } else {
907 throw TypeException(std::string("StructCell '") + std::string(Derived::kind_id) +
908 "': unknown field '" + key + "'",
909 std::string(Derived::kind_id),
910 key);
911 }
912 }
913
915 return out;
916 }
917
923 static const auto d = std::make_shared<StructCellTypeDescriptor<Derived, Payload>>(
924 Derived::display_info(), Derived::field_descriptors());
925 return d;
926 }
927};
928
983template <typename Derived>
984class SelfStructCell : public ICell {
985public:
986 using payload_type = Derived;
987
996 using storage_t = Derived;
997
1004 static constexpr bool allow_extra_fields = false;
1005
1006 [[nodiscard]] std::string_view kind() const override {
1007 return Derived::kind_id;
1008 }
1009
1010 [[nodiscard]] std::string to_string() const override {
1011 std::string out = "{";
1012 bool first = true;
1013 auto const& self = static_cast<Derived const&>(*this);
1014 for (auto const& f : Derived::field_descriptors()) {
1015 if (!first) {
1016 out += ", ";
1017 }
1018 f->to_string_into(out, self);
1019 first = false;
1020 }
1021 if constexpr (Derived::allow_extra_fields) {
1022 for (auto const& [key, ev] : cell_extras_) {
1023 if (!first) {
1024 out += ", ";
1025 }
1026 out += key;
1027 out += ": ";
1028 out += ev ? ev->to_string() : "<null>";
1029 first = false;
1030 }
1031 }
1032 out += "}";
1033 return out;
1034 }
1035
1036 [[nodiscard]] json_t to_json() const override {
1037 json_t v_obj = json_t::object();
1038 auto const& self = static_cast<Derived const&>(*this);
1039 for (auto const& f : Derived::field_descriptors()) {
1040 f->to_json_into(v_obj, self);
1041 }
1042 if constexpr (Derived::allow_extra_fields) {
1043 for (auto const& [key, ev] : cell_extras_) {
1044 v_obj[key] = ev ? ev->to_json() : json_t();
1045 }
1046 }
1047 json_t j{
1048 {ICell::KEY_KIND, Derived::kind_id},
1049 {ICell::KEY_VALUE, std::move(v_obj)},
1050 };
1051 this->inject_display_info(j);
1052 return j;
1053 }
1054
1055 [[nodiscard]] cell_t clone() const override {
1056 auto copy = std::make_shared<Derived>(static_cast<Derived const&>(*this));
1057 if constexpr (Derived::allow_extra_fields) {
1058 copy->cell_extras_.clear();
1059 for (auto const& [key, ev] : cell_extras_) {
1060 copy->cell_extras_.emplace(key, ev ? ev->clone() : nullptr);
1061 }
1062 }
1063 return copy;
1064 }
1065
1076 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
1077 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
1078 return k_cmp;
1079 }
1080 const auto* o = dynamic_cast<Derived const*>(&other);
1081 if (o == nullptr) {
1082 return std::partial_ordering::unordered;
1083 }
1084 auto const& self = static_cast<Derived const&>(*this);
1085 for (auto const& f : Derived::field_descriptors()) {
1086 if (const auto c = f->compare(self, *o); c != 0) {
1087 return c;
1088 }
1089 }
1090 if constexpr (Derived::allow_extra_fields) {
1091 auto ai = cell_extras_.begin();
1092 auto bi = o->cell_extras_.begin();
1093 while (ai != cell_extras_.end() && bi != o->cell_extras_.end()) {
1094 if (const auto kc = ai->first <=> bi->first; kc != 0) {
1095 return kc;
1096 }
1097 const bool an = ai->second == nullptr;
1098 if (const bool bn = bi->second == nullptr; an != bn) {
1099 return an ? std::partial_ordering::less : std::partial_ordering::greater;
1100 }
1101 if (!an) {
1102 if (const auto vc = *ai->second <=> *bi->second; vc != 0) {
1103 return vc;
1104 }
1105 }
1106 ++ai;
1107 ++bi;
1108 }
1109 return cell_extras_.size() <=> o->cell_extras_.size();
1110 }
1111 return std::partial_ordering::equivalent;
1112 }
1113
1114 [[nodiscard]] const std::map<std::string, cell_t>& cell_extras() const {
1115 return cell_extras_;
1116 }
1117
1130 static cell_t from_json(json_t const& j, ParcelRegistry const& reg) {
1131 if (!j.is_object()) {
1132 throw InvalidJsonException(std::string("Expected JSON object for SelfStructCell '") +
1133 std::string(Derived::kind_id) + "'",
1134 std::string(Derived::kind_id));
1135 }
1136
1137 const auto it_k = j.find(ICell::KEY_KIND);
1138 if (it_k == j.end() || !it_k->is_string()) {
1139 throw InvalidJsonException(std::string("SelfStructCell '") +
1140 std::string(Derived::kind_id) + "': missing/invalid 'k'",
1141 std::string(Derived::kind_id));
1142 }
1143 if (it_k->get<std::string_view>() != Derived::kind_id) {
1144 throw KindMismatchException(std::string("SelfStructCell '") +
1145 std::string(Derived::kind_id) +
1146 "': missing/invalid 'k'",
1147 std::string(Derived::kind_id));
1148 }
1149
1150 const auto it_v = j.find(ICell::KEY_VALUE);
1151 if (it_v == j.end() || !it_v->is_object()) {
1152 throw InvalidJsonException(std::string("SelfStructCell '") +
1153 std::string(Derived::kind_id) +
1154 "': missing/invalid 'v' (expected object)",
1155 std::string(Derived::kind_id));
1156 }
1157
1158 auto out = std::make_shared<Derived>();
1159 auto field_descs = Derived::field_descriptors();
1160
1161 std::map<std::string_view, IPayloadFieldDescriptor<Derived> const*> by_key;
1162 for (auto const& f : field_descs) {
1163 by_key.emplace(f->key(), f.get());
1164 }
1165
1166 for (auto const& f : field_descs) {
1167 const auto it = it_v->find(f->key());
1168 if (it == it_v->end()) {
1169 if (f->is_required()) {
1171 std::string("SelfStructCell '") + std::string(Derived::kind_id) +
1172 "': missing required field '" + std::string(f->key()) + "'",
1173 std::string(Derived::kind_id),
1174 std::string(f->key()));
1175 }
1176 continue;
1177 }
1178 f->from_json_into(*it, *out, reg);
1179 }
1180
1181 for (auto const& [key, raw] : it_v->items()) {
1182 if (by_key.contains(key)) {
1183 continue;
1184 }
1185 if constexpr (Derived::allow_extra_fields) {
1186 out->cell_extras_.emplace(key, reg.cell_from_json(raw));
1187 } else {
1188 throw TypeException(std::string("SelfStructCell '") +
1189 std::string(Derived::kind_id) + "': unknown field '" + key +
1190 "'",
1191 std::string(Derived::kind_id),
1192 key);
1193 }
1194 }
1195
1196 if (const auto it_d = j.find(ICell::KEY_DESCRIPTION); it_d != j.end()) {
1197 out->display_info_ = it_d->get<DisplayInfo>();
1198 }
1199 return out;
1200 }
1201
1207 static const auto d = std::make_shared<StructCellTypeDescriptor<Derived, Derived>>(
1208 Derived::display_info(), Derived::field_descriptors());
1209 return d;
1210 }
1211
1212 [[nodiscard]] std::optional<DisplayInfo> const& overridden_display_info() const override {
1213 return display_info_;
1214 }
1215
1216protected:
1226 if (display_info_) {
1228 }
1229 }
1230
1231 void set_display_info(std::optional<DisplayInfo> m) override {
1232 display_info_ = std::move(m);
1233 }
1234
1236 std::optional<DisplayInfo> display_info_;
1237
1239 std::map<std::string, cell_t> cell_extras_;
1240};
1241
1242} // namespace parcel
Core ICell interface, cell_t handle, BaseCell CRTP base, and CellLike concept.
std::shared_ptr< ICellTypeDescriptor > cell_type_descriptor_t
Shared handle to a runtime cell-type descriptor.
Definition cell.h:63
std::shared_ptr< ICell > cell_t
Shared handle to any ICell-derived value — the canonical cell pointer.
Definition cell.h:68
CRTP base that fills in the boilerplate of every cell-type descriptor.
Definition descriptor.h:165
json_t base_to_json() const
Common JSON skeleton (kind, display_info, category) reused by overrides.
Definition descriptor.h:202
Fluent builder used inside Derived::field_descriptors() to declare struct fields.
Definition struct.h:342
FieldsBuilder & icon(comms::Icon icon)
Set the icon display info of the most recently declared field to the typed icon.
Definition struct.h:425
FieldsBuilder & color(comms::Color color)
Set the color display info of the most recently declared field to the typed color.
Definition struct.h:449
FieldsBuilder & description(std::string v)
Set the description display info of the most recently declared field.
Definition struct.h:413
FieldsBuilder & field(std::string key)
Declare a field with an explicit cell wrapper CellT.
Definition struct.h:355
FieldsBuilder & optional()
Shorthand for is_required(false).
Definition struct.h:486
FieldsBuilder & field(std::string key)
Declare a field with cell wrapper inferred via default_cell_for.
Definition struct.h:388
FieldsBuilder & color(std::string const &v)
Set the color display info of the most recently declared field, parsing a color string (hex,...
Definition struct.h:462
FieldsBuilder & name(std::string v)
Set the name display info of the most recently declared field.
Definition struct.h:402
payload_field_descriptors_t build()
Move out the accumulated field descriptors.
Definition struct.h:543
FieldsBuilder & remove_field(const std::string_view key)
Drop a previously declared (or inherited) field by key.
Definition struct.h:533
FieldsBuilder & icon(std::string const &v)
Set the icon display info of the most recently declared field, parsing an Iconify set:name string (e....
Definition struct.h:438
FieldsBuilder & is_required(bool v=true)
Override the required flag of the most recently declared field.
Definition struct.h:476
FieldsBuilder & extend()
Splice in every field declared by another struct cell.
Definition struct.h:511
Adapter that lets a parent struct's payload field descriptor be reused by a derived struct.
Definition struct.h:254
DisplayInfo & mutable_display_info() override
Mutable access to this field's display info — used by FieldsBuilder.
Definition struct.h:284
InheritedFieldDescriptor(std::shared_ptr< IPayloadFieldDescriptor< ParentPayload > > inner)
Wrap a parent payload field descriptor.
Definition struct.h:265
DisplayInfo display_info() const override
Display info for this field.
Definition struct.h:274
void to_string_into(std::string &out, DerivedPayload const &p) const override
Append key: value for this field to the rendered string.
Definition struct.h:299
json_t to_json() const override
Serialize the field descriptor itself.
Definition struct.h:280
std::partial_ordering compare(DerivedPayload const &a, DerivedPayload const &b) const override
Three-way compare this field across two payload instances.
Definition struct.h:302
std::string_view kind() const override
Cell kind id for the field's value type.
Definition struct.h:271
void from_json_into(json_t const &field_json, DerivedPayload &p, ParcelRegistry const &reg) const override
Deserialize this field from field_json into p.
Definition struct.h:294
std::string_view key() const override
JSON key under which this field appears in "v".
Definition struct.h:268
void to_json_into(json_t &v_obj, DerivedPayload const &p) const override
Serialize this field from p into the value object v_obj.
Definition struct.h:291
void set_required(const bool r) override
Override the field's required flag.
Definition struct.h:287
bool is_required() const override
Whether the field must be present on deserialization.
Definition struct.h:277
JSON shape was wrong (missing/non-string k, missing v, etc.).
Definition error.h:158
k field was present but did not match the expected kind.
Definition error.h:168
Concrete payload field descriptor backed by a member pointer.
Definition struct.h:125
void to_string_into(std::string &out, Payload const &p) const override
Append key: value for this field to the rendered string.
Definition struct.h:224
bool is_required() const override
Whether the field must be present on deserialization.
Definition struct.h:155
void set_required(const bool r) override
Override the field's required flag.
Definition struct.h:171
void to_json_into(json_t &v_obj, Payload const &p) const override
Serialize this field from p into the value object v_obj.
Definition struct.h:175
std::partial_ordering compare(Payload const &a, Payload const &b) const override
Three-way compare this field across two payload instances.
Definition struct.h:201
json_t to_json() const override
Serialize the field descriptor itself.
Definition struct.h:159
DisplayInfo display_info() const override
Display info for this field.
Definition struct.h:152
std::string_view key() const override
JSON key under which this field appears in "v".
Definition struct.h:146
MemberFieldDescriptor(FieldT Payload::*member_ptr, std::string key, DisplayInfo info)
Construct with the member pointer, JSON key, and (initial) display info.
Definition struct.h:142
void from_json_into(json_t const &field_json, Payload &p, ParcelRegistry const &reg) const override
Deserialize this field from field_json into p.
Definition struct.h:189
DisplayInfo & mutable_display_info() override
Mutable access to this field's display info — used by FieldsBuilder.
Definition struct.h:168
std::string_view kind() const override
Cell kind id for the field's value type.
Definition struct.h:149
Required struct field absent from the JSON payload.
Definition error.h:182
Runtime catalog of cell-type descriptors, keyed by wire kind id.
Definition registry.h:115
cell_t cell_from_json(json_t const &j) const
Deserialize any registered cell from JSON, dispatching by "k".
Definition registry.h:266
CRTP base for struct cells that are their own payload.
Definition struct.h:984
std::map< std::string, cell_t > cell_extras_
Unknown keys retained when Derived::allow_extra_fields is true.
Definition struct.h:1239
std::partial_ordering compare(ICell const &other) const override
Three-way comparison over field values (and cell_extras_ when allow_extra_fields is true); ignores di...
Definition struct.h:1076
std::optional< DisplayInfo > const & overridden_display_info() const override
Read-only access to this cell's optional display info.
Definition struct.h:1212
json_t to_json() const override
Serialize this cell to its canonical JSON representation.
Definition struct.h:1036
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize the self-payload struct cell from JSON.
Definition struct.h:1130
std::string to_string() const override
Render the cell's value as a compact human-readable string.
Definition struct.h:1010
void set_display_info(std::optional< DisplayInfo > m) override
Replace this cell's display info in place.
Definition struct.h:1231
static constexpr bool allow_extra_fields
Whether unknown JSON keys are tolerated.
Definition struct.h:1004
void inject_display_info(json_t &j) const
Copy this cell's display info (if any) into the JSON object under "d".
Definition struct.h:1225
std::string_view kind() const override
Wire-stable kind identifier for this cell.
Definition struct.h:1006
static cell_type_descriptor_t descriptor()
Cached descriptor for this self-payload struct cell.
Definition struct.h:1206
std::optional< DisplayInfo > display_info_
Optional display info; omitted from JSON when empty.
Definition struct.h:1236
cell_t clone() const override
Deep-copy this cell.
Definition struct.h:1055
Derived storage_t
Storage-type alias used by descriptors that probe T::storage_t.
Definition struct.h:996
Descriptor for StructCell<Derived, Payload>.
Definition struct.h:587
descriptor::CellCategory category() const override
Coarse classification (primitive, list, struct, …).
Definition struct.h:602
payload_field_descriptors_t const & payload_fields() const
Read-only access to the typed payload-field descriptors.
Definition struct.h:638
std::vector< std::string_view > sub_kinds() const override
Sub-kind ids reachable from this descriptor.
Definition struct.h:628
StructCellTypeDescriptor(DisplayInfo info, payload_field_descriptors_t fields)
Construct with display info and the field descriptor list.
Definition struct.h:599
cell_t cell_from_json(json_t const &j, ParcelRegistry const &reg) const override
Construct a cell from JSON, dispatching to the concrete from_json.
Definition struct.h:606
json_t to_json() const override
Serialize the descriptor itself (kind + display info + category + extras).
Definition struct.h:610
field_descriptors_t fields() const override
Field descriptors keyed by JSON key.
Definition struct.h:620
CRTP base for user-defined struct cells.
Definition struct.h:685
static constexpr bool allow_extra_fields
Whether unknown JSON keys are tolerated.
Definition struct.h:700
static DisplayInfo display_info()
Default cell-level display info (empty).
Definition struct.h:721
static constexpr std::string_view struct_id
Bare struct id, lifted from the StructId template arg.
Definition struct.h:703
cell_t clone() const override
Deep-copy this cell.
Definition struct.h:771
static constexpr std::string_view kind_id
Wire kind id of this struct cell ("s:" + struct_id).
Definition struct.h:712
std::partial_ordering compare(ICell const &other) const override
Three-way comparison over field values (and extras when allow_extra_fields is true); ignores display ...
Definition struct.h:798
static cell_type_descriptor_t descriptor()
Cached descriptor for this struct cell.
Definition struct.h:922
static cell_t from_json(json_t const &j, ParcelRegistry const &reg)
Deserialize the struct cell from JSON.
Definition struct.h:850
std::string to_string() const override
Render the cell's value as a compact human-readable string.
Definition struct.h:728
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition struct.h:753
std::map< std::string, cell_t > extras
Unknown keys retained when Derived::allow_extra_fields is true.
Definition struct.h:726
Typed value (cast, struct field, etc.) failed conversion.
Definition error.h:190
comms::DisplayInfo DisplayInfo
Display info attached to a cell or descriptor — re-exported from comms::DisplayInfo.
Definition common.h:75
constexpr std::string_view id_join_lit_v
Convenience alias yielding the joined std::string_view.
Definition common.h:140
Concept matching types for which a default_cell_for mapping exists.
Definition defaults.h:69
The default_cell_for<T> trait that drives FieldsBuilder field-type inference.
Runtime cell-type descriptors and the schema-graph mix-ins (IHasFields, ISubTypes).
std::map< std::string, field_descriptor_t > field_descriptors_t
Ordered map of field key → descriptor.
Definition descriptor.h:125
CellCategory
Coarse runtime classification of a cell type.
Definition descriptor.h:35
nlohmann::json json_t
Project-wide alias for nlohmann::json.
Definition json.h:19
TypedListCell<T> and heterogeneous ListCell with their descriptors.
TypedMapCell<T> and heterogeneous MapCell with their descriptors.
PrimitiveCell<T> plus the per-storage PrimitiveTraits<T> specializations.
ParcelRegistry, Definition, and BuiltinsOptions for polymorphic dispatch.
CRTP base providing default to_json / clone / kind plumbing on top of a storage type.
Definition cell.h:343
std::string_view kind() const override
Definition cell.h:411
static void absorb_display_info(json_t const &j, Out &out)
Read "d" (if present) from a JSON object and assign it onto a cell.
Definition cell.h:499
Payload value
Held value of the cell.
Definition cell.h:348
void inject_display_info(json_t &j) const
Copy this cell's display info (if any) into the JSON object under "d".
Definition cell.h:486
Polymorphic root of every parcel cell.
Definition cell.h:84
static constexpr std::string_view KEY_DESCRIPTION
JSON key for the optional display info block ("d").
Definition cell.h:90
static constexpr std::string_view KEY_VALUE
JSON key for the value payload ("v").
Definition cell.h:88
virtual std::string_view kind() const =0
Wire-stable kind identifier for this cell.
static constexpr std::string_view KEY_KIND
JSON key for the kind id ("k").
Definition cell.h:86
Runtime descriptor for a single struct field.
Definition descriptor.h:99
virtual std::string_view key() const =0
JSON key under which this field appears in "v".
Mix-in declaring a descriptor exposes a static set of named fields.
Definition descriptor.h:133
Field-descriptor extension that knows how to read/write a specific Payload.
Definition struct.h:67
virtual DisplayInfo & mutable_display_info()=0
Mutable access to this field's display info — used by FieldsBuilder.
virtual void to_string_into(std::string &out, Payload const &p) const =0
Append key: value for this field to the rendered string.
virtual void to_json_into(json_t &v_obj, Payload const &p) const =0
Serialize this field from p into the value object v_obj.
virtual std::partial_ordering compare(Payload const &a, Payload const &b) const =0
Three-way compare this field across two payload instances.
virtual void set_required(bool r)=0
Override the field's required flag.
virtual void from_json_into(json_t const &field_json, Payload &p, ParcelRegistry const &reg) const =0
Deserialize this field from field_json into p.
Mix-in declaring a descriptor refers to other registered cell kinds.
Definition descriptor.h:148