parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
cell.h
Go to the documentation of this file.
1#pragma once
2
15#include <parcel/common.h>
16#include <parcel/error.h>
17
18#include <compare>
19#include <concepts>
20#include <cstddef>
21#include <functional>
22#include <memory>
23#include <optional>
24#include <stdexcept>
25#include <string>
26#include <string_view>
27#include <type_traits>
28#include <utility>
29#include <variant>
30
31#if PARCEL_HAS_EXPECTED
32#include <expected>
33#endif
34
35namespace parcel {
36class ParcelRegistry;
37struct ICellTypeDescriptor;
38
39namespace detail {
40template <typename T>
41struct is_std_variant : std::false_type {};
42template <typename... Ts>
43struct is_std_variant<std::variant<Ts...>> : std::true_type {};
44
45// Used to suppress the BaseCell::compare default for storage types whose
46// `operator==` is unconstrained but instantiates per-element comparisons —
47// they would emit hard errors on substitution even though the cell type
48// (UnionCell, TypedListCell, TypedMapCell) overrides compare(). Names match
49// nominally; specializations are added for the std container types we
50// expose as cell storage.
51template <typename T>
52struct disables_basecell_compare : std::false_type {};
53} // namespace detail
54
55template <typename T>
56inline constexpr bool is_std_variant_v = detail::is_std_variant<std::remove_cvref_t<T>>::value;
57
58template <typename T>
59inline constexpr bool disables_basecell_compare_v =
60 detail::disables_basecell_compare<std::remove_cvref_t<T>>::value;
61
63using cell_type_descriptor_t = std::shared_ptr<ICellTypeDescriptor>;
64
65struct ICell;
66
68using cell_t = std::shared_ptr<ICell>;
69
84struct ICell {
86 static constexpr std::string_view KEY_KIND = "k";
88 static constexpr std::string_view KEY_VALUE = "v";
90 static constexpr std::string_view KEY_DESCRIPTION = "d";
91
92 virtual ~ICell() = default;
93
98 [[nodiscard]] virtual std::string_view kind() const = 0;
99
104 [[nodiscard]] virtual std::optional<DisplayInfo> const& overridden_display_info() const = 0;
105
110 [[nodiscard]] virtual json_t to_json() const = 0;
111
116 [[nodiscard]] virtual std::string to_string() const = 0;
117
122 [[nodiscard]] virtual std::string to_formatted_string() const {
123 return to_string();
124 }
125
130 [[nodiscard]] virtual cell_t clone() const = 0;
131
145 [[nodiscard]] virtual std::partial_ordering compare(ICell const& other) const = 0;
146
157 [[nodiscard]] virtual std::size_t hash_value() const noexcept {
158 const std::size_t hk = std::hash<std::string_view>{}(this->kind());
159 try {
160 const auto j = this->to_json();
161 const auto it = j.find(KEY_VALUE);
162 const std::size_t hv =
163 (it != j.end()) ? std::hash<std::string>{}(it->dump()) : std::size_t{0};
164 return hk ^ (hv + 0x9e3779b97f4a7c15ULL + (hk << 6) + (hk >> 2));
165 } catch (...) {
166 return hk;
167 }
168 }
169
171 friend bool operator==(ICell const& a, ICell const& b) {
172 return a.compare(b) == std::partial_ordering::equivalent;
173 }
174
176 friend std::partial_ordering operator<=>(ICell const& a, ICell const& b) {
177 return a.compare(b);
178 }
179
180 // Immutable builders: each clones the cell, mutates the clone's display info,
181 // and returns the new cell_t. The original is untouched.
182
188 [[nodiscard]] cell_t with_display_info(DisplayInfo m) const {
189 auto c = clone();
190 c->set_display_info(std::move(m));
191 return c;
192 }
193
199 [[nodiscard]] cell_t with_name(std::string v) const {
200 auto m = overridden_display_info().value_or(DisplayInfo{});
201 m.name = std::move(v);
202 return with_display_info(std::move(m));
203 }
204
210 [[nodiscard]] cell_t with_description(std::string v) const {
211 auto m = overridden_display_info().value_or(DisplayInfo{});
212 m.description = std::move(v);
213 return with_display_info(std::move(m));
214 }
215
221 [[nodiscard]] cell_t with_icon(comms::Icon icon) const {
222 auto m = overridden_display_info().value_or(DisplayInfo{});
223 m.icon = icon;
224 return with_display_info(std::move(m));
225 }
226
234 [[nodiscard]] cell_t with_icon(std::string const& v) const {
235 return with_icon(comms::Icon::from(v));
236 }
237
243 [[nodiscard]] cell_t with_color(comms::Color color) const {
244 auto m = overridden_display_info().value_or(DisplayInfo{});
245 m.color = color;
246 return with_display_info(std::move(m));
247 }
248
256 [[nodiscard]] cell_t with_color(std::string const& v) const {
257 const auto parsed = comms::Color::parse(v);
258 if (!parsed) {
259 throw InvalidJsonException("with_color: '" + v + "' is not a valid color");
260 }
261 return with_color(*parsed);
262 }
263
264protected:
271 virtual void set_display_info(std::optional<DisplayInfo> m) = 0;
272
273 template <typename, typename>
274 friend struct BaseCell;
275
276 template <typename>
277 friend class SelfStructCell;
278};
279
291template <typename T>
292 requires std::derived_from<T, ICell>
293inline void to_json(json_t& j, T const& v) {
294 j = v.to_json();
295}
296
306template <typename T>
307 requires std::derived_from<T, ICell>
308inline void to_json(json_t& j, std::shared_ptr<T> const& v) {
309 j = v ? v->to_json() : json_t();
310}
311
321template <typename T>
322concept CellLike = std::derived_from<T, ICell> && requires {
323 { T::kind_id } -> std::convertible_to<std::string_view>;
324 {
325 T::from_json(std::declval<json_t const&>(), std::declval<ParcelRegistry const&>())
326 } -> std::convertible_to<cell_t>;
327 { T::descriptor() } -> std::convertible_to<cell_type_descriptor_t>;
328};
329
342template <typename Derived, typename Storage>
343struct BaseCell : ICell {
344 using derived_t = Derived;
345 using storage_t = Storage;
346
348 Storage value{};
349
350 BaseCell() = default;
351
360 template <typename U>
361 requires std::constructible_from<Storage, U&&>
362 explicit(false) BaseCell(U&& v) : value(std::forward<U>(v)) {}
363
370 template <typename U>
371 requires std::assignable_from<Storage&, U&&>
372 Derived& operator=(U&& v) {
373 value = std::forward<U>(v);
374 return static_cast<Derived&>(*this);
375 }
376
383 template <typename... Args>
384 requires std::constructible_from<Derived, Args&&...>
385 static std::shared_ptr<Derived> of(Args&&... args) {
386 return std::make_shared<Derived>(std::forward<Args>(args)...);
387 }
388
395 template <typename... Args>
396 requires std::constructible_from<Derived, Args&&...>
397 static std::unique_ptr<Derived> unique(Args&&... args) {
398 return std::make_unique<Derived>(std::forward<Args>(args)...);
399 }
400
402 Storage& get() {
403 return value;
404 }
405
407 Storage const& get() const {
408 return value;
409 }
410
411 [[nodiscard]] std::string_view kind() const override {
412 return Derived::kind_id;
413 }
414
415 [[nodiscard]] std::optional<DisplayInfo> const& overridden_display_info() const override {
416 return display_info_;
417 }
418
419 [[nodiscard]] cell_t clone() const override {
420 return std::make_shared<Derived>(static_cast<Derived const&>(*this));
421 }
422
435 [[nodiscard]] std::partial_ordering compare(ICell const& other) const override {
436 if (const auto k_cmp = this->kind() <=> other.kind(); k_cmp != 0) {
437 return k_cmp;
438 }
439 const auto* o = dynamic_cast<Derived const*>(&other);
440 if (o == nullptr) {
441 return std::partial_ordering::unordered;
442 }
443 // std::variant<T...>'s operator== is unconstrained and instantiates
444 // its alternatives' ==; same for std::vector / std::map. If an
445 // element type lacks ==, the body fails to compile. The cell types
446 // built on those storages (UnionCell, TypedListCell, TypedMapCell,
447 // ListCell, MapCell) override compare() with their own dispatch —
448 // skip the body here so BaseCell stays instantiable.
449 if constexpr (!is_std_variant_v<Storage> && !disables_basecell_compare_v<Storage>) {
450 if constexpr (std::three_way_comparable<Storage>) {
451 return this->value <=> o->value;
452 } else if constexpr (std::equality_comparable<Storage>) {
453 return this->value == o->value ? std::partial_ordering::equivalent
454 : std::partial_ordering::unordered;
455 }
456 }
457 return std::partial_ordering::unordered;
458 }
459
470 [[nodiscard]] json_t to_json() const override {
471 if constexpr (requires(Storage const& s) { json_t(s); }) {
472 json_t j{{KEY_KIND, kind()}, {KEY_VALUE, this->value}};
474 return j;
475 } else {
476 throw std::runtime_error(
477 "BaseCell::to_json() requires Derived to override or Storage to be "
478 "JSON-convertible");
479 }
480 }
481
487 if (display_info_) {
489 }
490 }
491
498 template <typename Out>
499 static void absorb_display_info(json_t const& j, Out& out) {
500 if (const auto it = j.find(KEY_DESCRIPTION); it != j.end()) {
501 out->set_display_info(it->get<DisplayInfo>());
502 }
503 }
504
505protected:
507 std::optional<DisplayInfo> display_info_;
508
509 void set_display_info(std::optional<DisplayInfo> m) override {
510 display_info_ = std::move(m);
511 }
512
525 template <typename T>
526 static T cell_from_json(const json_t& j, const std::string_view expected_kind) {
527 if (!j.is_object()) {
528 throw InvalidJsonException("Expected JSON object for ICell",
529 std::string(expected_kind));
530 }
531
532 const auto it_kind = j.find(KEY_KIND);
533 if (it_kind == j.end() || !it_kind->is_string()) {
534 throw InvalidJsonException("Missing or invalid 'k' field in ICell JSON",
535 std::string(expected_kind));
536 }
537
538 if (const auto kind = it_kind->get<std::string_view>(); kind != expected_kind) {
539 throw KindMismatchException("Unexpected kind in ICell JSON: expected '" +
540 std::string(expected_kind) + "', got '" +
541 std::string(kind) + "'",
542 std::string(expected_kind));
543 }
544
545 const auto it_value = j.find(KEY_VALUE);
546 if (it_value == j.end()) {
547 throw InvalidJsonException("Missing 'v' field in ICell JSON",
548 std::string(expected_kind));
549 }
550
551 return it_value->get<T>();
552 }
553
554public:
571 static cell_t from_json_strict(json_t const& j) {
572 auto v = cell_from_json<Storage>(j, Derived::kind_id);
573 auto cell = std::make_shared<Derived>(std::move(v));
575 return cell;
576 }
577};
578
587template <typename C>
588 requires std::derived_from<C, ICell>
589[[nodiscard]] std::shared_ptr<C> cell_cast(cell_t const& c) {
590 if (!c) {
591 throw TypeException("cell_cast: null cell", std::string(C::kind_id));
592 }
593 auto out = std::dynamic_pointer_cast<C>(c);
594 if (!out) {
595 throw KindMismatchException(std::string("cell_cast: expected '") + std::string(C::kind_id) +
596 "' but got '" + std::string(c->kind()) + "'",
597 std::string(C::kind_id));
598 }
599 return out;
600}
601
610template <typename C>
611 requires std::derived_from<C, ICell>
612[[nodiscard]] std::shared_ptr<C> cell_cast_or(cell_t const& c, std::shared_ptr<C> fallback) {
613 if (!c) {
614 return fallback;
615 }
616 auto out = std::dynamic_pointer_cast<C>(c);
617 return out ? out : std::move(fallback);
618}
619
620#if PARCEL_HAS_EXPECTED
628template <typename C>
629 requires std::derived_from<C, ICell>
630[[nodiscard]] std::expected<std::shared_ptr<C>, ParcelError> try_cell_cast(cell_t const& c) {
631 if (!c) {
632 return std::unexpected(ParcelError::make(ParcelError::Code::TypeError, "null cell"));
633 }
634 auto out = std::dynamic_pointer_cast<C>(c);
635 if (!out) {
637 std::string("expected '") +
638 std::string(C::kind_id) + "' but got '" +
639 std::string(c->kind()) + "'",
640 std::string(C::kind_id)));
641 }
642 return out;
643}
644#endif
645
654template <typename C>
655 requires std::derived_from<C, ICell>
656[[nodiscard]] typename C::storage_t cell_cast_value(cell_t const& c) {
657 return cell_cast<C>(c)->value;
658}
659
670template <typename C>
671 requires std::derived_from<C, ICell>
672[[nodiscard]] std::optional<typename C::storage_t> as(cell_t const& c) noexcept {
673 if (!c) {
674 return std::nullopt;
675 }
676 auto* typed = dynamic_cast<C const*>(c.get());
677 if (typed == nullptr) {
678 return std::nullopt;
679 }
680 return typed->value;
681}
682
691template <typename C>
692 requires std::derived_from<C, ICell>
693[[nodiscard]] typename C::storage_t value_or(cell_t const& c, typename C::storage_t fallback) {
694 if (!c) {
695 return fallback;
696 }
697 auto* typed = dynamic_cast<C const*>(c.get());
698 return typed ? typed->value : std::move(fallback);
699}
700
704[[nodiscard]] inline json_t to_json(cell_t const& c) {
705 return c ? c->to_json() : json_t{};
706}
707
711[[nodiscard]] inline std::string to_string(cell_t const& c) {
712 return c ? c->to_string() : std::string{"<null>"};
713}
714
718[[nodiscard]] inline std::string to_string_pretty(cell_t const& c) {
719 return c ? c->to_formatted_string() : std::string{"<null>"};
720}
721
722} // namespace parcel
723
724namespace std {
725
734template <>
735struct hash<parcel::ICell> {
736 [[nodiscard]] size_t operator()(parcel::ICell const& c) const noexcept {
737 return c.hash_value();
738 }
739};
740
742template <>
743struct hash<parcel::cell_t> {
744 [[nodiscard]] size_t operator()(parcel::cell_t const& c) const noexcept {
745 return c ? std::hash<parcel::ICell>{}(*c) : std::size_t{0};
746 }
747};
748
749} // namespace std
750
751namespace parcel {
752
759[[nodiscard]] inline std::size_t hash_cell(ICell const& c) noexcept {
760 return std::hash<ICell>{}(c);
761}
762
763[[nodiscard]] inline std::size_t hash_cell(cell_t const& c) noexcept {
764 return std::hash<cell_t>{}(c);
765}
766
767} // namespace parcel
std::string to_string(cell_t const &c)
Free to_string over cell_t; returns "<null>" for null handles.
Definition cell.h:711
std::shared_ptr< ICellTypeDescriptor > cell_type_descriptor_t
Shared handle to a runtime cell-type descriptor.
Definition cell.h:63
void to_json(json_t &j, T const &v)
ADL hook that lets nlohmann serialize any ICell-derived cell.
Definition cell.h:293
C::storage_t cell_cast_value(cell_t const &c)
Cast and unwrap to the inner storage value in one call.
Definition cell.h:656
std::shared_ptr< C > cell_cast(cell_t const &c)
Type-safe dynamic_cast from cell_t to a concrete cell type.
Definition cell.h:589
std::shared_ptr< ICell > cell_t
Shared handle to any ICell-derived value — the canonical cell pointer.
Definition cell.h:68
std::optional< typename C::storage_t > as(cell_t const &c) noexcept
std::optional view over a cell's inner storage.
Definition cell.h:672
std::shared_ptr< C > cell_cast_or(cell_t const &c, std::shared_ptr< C > fallback)
cell_cast that returns fallback on failure rather than throwing.
Definition cell.h:612
C::storage_t value_or(cell_t const &c, typename C::storage_t fallback)
Extract c's storage as C, falling back to fallback on failure.
Definition cell.h:693
std::size_t hash_cell(ICell const &c) noexcept
Hash a cell value compatibly with operator==.
Definition cell.h:759
std::string to_string_pretty(cell_t const &c)
Multi-line to_string over cell_t; returns "<null>" for null handles.
Definition cell.h:718
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
CRTP base for struct cells that are their own payload.
Definition struct.h:984
Typed value (cast, struct field, etc.) failed conversion.
Definition error.h:190
The shared vocabulary aliases re-exported from commons and the compile-time kind-id machinery.
comms::DisplayInfo DisplayInfo
Display info attached to a cell or descriptor — re-exported from comms::DisplayInfo.
Definition common.h:75
Concept naming the static interface every cell type must expose.
Definition cell.h:322
auto cell(T &&v)
Wrap a raw value into its default cell, returning a shared_ptr to the cell.
Definition defaults.h:192
ParcelError and the non-throwing surface that returns std::expected.
nlohmann::json json_t
Project-wide alias for nlohmann::json.
Definition json.h:19
CRTP base providing default to_json / clone / kind plumbing on top of a storage type.
Definition cell.h:343
std::optional< DisplayInfo > display_info_
Optional display info; omitted from JSON when empty.
Definition cell.h:507
static std::unique_ptr< Derived > unique(Args &&... args)
Construct a unique_ptr<Derived> forwarding the arguments.
Definition cell.h:397
static cell_t from_json_strict(json_t const &j)
Strict shorthand for the recurring cell_from_json + make_shared.
Definition cell.h:571
Derived & operator=(U &&v)
Assign to the storage from v.
Definition cell.h:372
cell_t clone() const override
Deep-copy this cell.
Definition cell.h:419
static std::shared_ptr< Derived > of(Args &&... args)
Construct a shared_ptr<Derived> forwarding the arguments.
Definition cell.h:385
std::partial_ordering compare(ICell const &other) const override
Default three-way comparison: kind first, then storage value.
Definition cell.h:435
Storage & get()
Mutable access to the held storage.
Definition cell.h:402
Storage const & get() const
Read-only access to the held storage.
Definition cell.h:407
std::string_view kind() const override
Wire-stable kind identifier for this cell.
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
Storage value
Held value of the cell.
Definition cell.h:348
std::optional< DisplayInfo > const & overridden_display_info() const override
Read-only access to this cell's optional display info.
Definition cell.h:415
void set_display_info(std::optional< DisplayInfo > m) override
Replace this cell's display info in place.
Definition cell.h:509
json_t to_json() const override
Default JSON serialization for cells with JSON-convertible storage.
Definition cell.h:470
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
static T cell_from_json(const json_t &j, const std::string_view expected_kind)
Validate a wrapped {"k","v"} JSON object and extract its "v" as T.
Definition cell.h:526
Polymorphic root of every parcel cell.
Definition cell.h:84
virtual cell_t clone() const =0
Deep-copy this cell.
virtual std::string to_formatted_string() const
Render the cell as a multi-line, indented string.
Definition cell.h:122
virtual std::partial_ordering compare(ICell const &other) const =0
Three-way compare against another cell.
virtual void set_display_info(std::optional< DisplayInfo > m)=0
Replace this cell's display info in place.
virtual std::string to_string() const =0
Render the cell's value as a compact human-readable string.
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
friend bool operator==(ICell const &a, ICell const &b)
Equality through compare; ignores display info.
Definition cell.h:171
virtual json_t to_json() const =0
Serialize this cell to its canonical JSON representation.
cell_t with_name(std::string v) const
Return a deep copy with name set to v.
Definition cell.h:199
virtual std::string_view kind() const =0
Wire-stable kind identifier for this cell.
cell_t with_icon(std::string const &v) const
Return a deep copy with icon parsed from an Iconify set:name string (e.g.
Definition cell.h:234
virtual std::size_t hash_value() const noexcept
Equality-consistent hash that mirrors compare's display-info-insensitivity.
Definition cell.h:157
cell_t with_icon(comms::Icon icon) const
Return a deep copy with icon set to the typed icon.
Definition cell.h:221
cell_t with_display_info(DisplayInfo m) const
Return a deep copy with the entire display info block replaced.
Definition cell.h:188
cell_t with_description(std::string v) const
Return a deep copy with description set to v.
Definition cell.h:210
virtual std::optional< DisplayInfo > const & overridden_display_info() const =0
Read-only access to this cell's optional display info.
cell_t with_color(std::string const &v) const
Return a deep copy with color parsed from a color string (hex like "#ffcc00", a CSS-functional form,...
Definition cell.h:256
cell_t with_color(comms::Color color) const
Return a deep copy with color set to the typed color.
Definition cell.h:243
friend std::partial_ordering operator<=>(ICell const &a, ICell const &b)
Three-way comparison through compare; ignores display info.
Definition cell.h:176
static constexpr std::string_view KEY_KIND
JSON key for the kind id ("k").
Definition cell.h:86
static ParcelError make(const Code c, std::string msg, std::string kind_id={}, std::string fld={})
Construct from code, message, and optional kind/field tags.
Definition error.h:107
@ TypeError
A typed value (e.g.
@ KindMismatch
JSON "k" did not match the expected kind.