parcel 0.2.2
Wrappable, wire-transferable C++23 value system with JSON serialization
Loading...
Searching...
No Matches
registry.h
Go to the documentation of this file.
1#pragma once
2
15#include <parcel/cell.h>
16#include <parcel/descriptor.h>
17#include <parcel/error.h>
18#include <parcel/json.h>
19
20#include <cstddef>
21#include <functional>
22#include <map>
23#include <memory>
24#include <ranges>
25#include <stdexcept>
26#include <string>
27#include <string_view>
28#include <typeindex>
29#include <typeinfo>
30#include <utility>
31#include <vector>
32
33#if PARCEL_HAS_EXPECTED
34#include <expected>
35#endif
36
37namespace parcel {
38
52 bool primitives = true;
54 bool collections = true;
59 bool typed_collections = true;
64 bool std = true;
70 bool commons = true;
75 bool ulid = true;
76};
77
84struct Definition {
88 std::map<std::string, cell_type_descriptor_t, std::less<>> referenced;
89
94 [[nodiscard]] json_t to_json() const {
95 json_t refs = json_t::object();
96 for (auto const& [k, d] : referenced) {
97 refs[k] = d->to_json();
98 }
99 return json_t{
100 {"root", root->to_json()},
101 {"referenced", std::move(refs)},
102 };
103 }
104};
105
116 std::map<std::string, cell_type_descriptor_t, std::less<>> by_kind_;
117
118public:
123 explicit ParcelRegistry(BuiltinsOptions opts = {});
124
131
143 template <typename... Ds>
145 (register_kind(std::forward<Ds>(ds)), ...);
146 return *this;
147 }
148
159 template <typename... Cs>
161 (register_kind(Cs::descriptor()), ...);
162 return *this;
163 }
164
170 [[nodiscard]] cell_type_descriptor_t find(std::string_view kind) const;
171
178 [[nodiscard]] cell_t cell_from_json(json_t const& j) const;
179
180#if PARCEL_HAS_EXPECTED
194 [[nodiscard]] std::expected<cell_t, ParcelError> try_cell_from_json(json_t const& j) const;
195
202 [[nodiscard]] std::expected<cell_t, ParcelError> try_cell_from_string(std::string_view s) const;
203#endif
204
206 [[nodiscard]] std::vector<cell_type_descriptor_t> all() const;
207
209 [[nodiscard]] std::vector<std::string_view> kinds() const;
210
212 [[nodiscard]] std::size_t count() const noexcept;
213
218 [[nodiscard]] bool contains(std::string_view kind) const;
219
224 [[nodiscard]] std::vector<cell_type_descriptor_t>
225 find_by_category(descriptor::CellCategory c) const;
226
231 [[nodiscard]] std::vector<cell_type_descriptor_t> find_by_storage(std::type_index ti) const;
232
237 template <typename T>
238 [[nodiscard]] std::vector<cell_type_descriptor_t> find_by_storage() const {
239 return find_by_storage(std::type_index(typeid(T)));
240 }
241
249 [[nodiscard]] Definition define(std::string_view kind) const;
250};
251
253 if (d == nullptr) {
254 throw std::runtime_error("ParcelRegistry::register_kind: null descriptor");
255 }
256 by_kind_.insert_or_assign(std::string(d->kind()), std::move(d));
257}
258
259inline cell_type_descriptor_t ParcelRegistry::find(const std::string_view kind) const {
260 if (const auto it = by_kind_.find(kind); it != by_kind_.end()) {
261 return it->second;
262 }
263 return nullptr;
264}
265
267 if (!j.is_object()) {
268 throw InvalidJsonException("ParcelRegistry::cell_from_json: expected JSON object");
269 }
270 const auto it_k = j.find(ICell::KEY_KIND);
271 if (it_k == j.end() || !it_k->is_string()) {
272 throw InvalidJsonException("ParcelRegistry::cell_from_json: missing/invalid 'k'");
273 }
274 const auto kind = it_k->get<std::string_view>();
275 const auto desc = find(kind);
276 if (desc == nullptr) {
277 throw UnknownKindException("ParcelRegistry::cell_from_json: unknown kind '" +
278 std::string(kind) + "'",
279 std::string(kind));
280 }
281 return desc->cell_from_json(j, *this);
282}
283
284#if PARCEL_HAS_EXPECTED
285inline std::expected<cell_t, ParcelError>
286ParcelRegistry::try_cell_from_json(json_t const& j) const {
287 if (!j.is_object()) {
288 return std::unexpected(
289 ParcelError::make(ParcelError::Code::InvalidJson, "expected JSON object"));
290 }
291 const auto it_k = j.find(ICell::KEY_KIND);
292 if (it_k == j.end() || !it_k->is_string()) {
293 return std::unexpected(
294 ParcelError::make(ParcelError::Code::InvalidJson, "missing or non-string 'k'"));
295 }
296 const auto kind = it_k->get<std::string_view>();
297 const auto desc = find(kind);
298 if (desc == nullptr) {
299 return std::unexpected(
300 ParcelError::make(ParcelError::Code::UnknownKind, "unknown kind", std::string(kind)));
301 }
302 try {
303 return desc->cell_from_json(j, *this);
304 } catch (ParcelException const& e) {
305 // Preserve the structured code / kind / field carried by the
306 // exception. If the throw site didn't tag a kind, fall back to the
307 // dispatch kind we already know.
308 auto err = e.to_error();
309 if (err.kind.empty()) {
310 err.kind = std::string(kind);
311 }
312 return std::unexpected(std::move(err));
313 } catch (std::exception const& e) {
314 return std::unexpected(
315 ParcelError::make(ParcelError::Code::TypeError, e.what(), std::string(kind)));
316 } catch (...) {
317 return std::unexpected(ParcelError::make(
318 ParcelError::Code::TypeError, "unknown deserialization error", std::string(kind)));
319 }
320}
321
322inline std::expected<cell_t, ParcelError>
323ParcelRegistry::try_cell_from_string(std::string_view s) const {
324 json_t j;
325 try {
326 j = json_t::parse(s);
327 } catch (std::exception const& e) {
328 return std::unexpected(ParcelError::make(ParcelError::Code::InvalidJson, e.what()));
329 } catch (...) {
330 return std::unexpected(
332 }
333 return try_cell_from_json(j);
334}
335#endif
336
337inline std::vector<cell_type_descriptor_t> ParcelRegistry::all() const {
338 std::vector<cell_type_descriptor_t> out;
339 out.reserve(by_kind_.size());
340 for (const auto& d : by_kind_ | std::views::values) {
341 out.push_back(d);
342 }
343 return out;
344}
345
346inline std::vector<std::string_view> ParcelRegistry::kinds() const {
347 std::vector<std::string_view> out;
348 out.reserve(by_kind_.size());
349 for (const auto& d : by_kind_ | std::views::values) {
350 out.push_back(d->kind());
351 }
352 return out;
353}
354
355inline std::size_t ParcelRegistry::count() const noexcept {
356 return by_kind_.size();
357}
358
359inline bool ParcelRegistry::contains(const std::string_view kind) const {
360 return by_kind_.contains(kind);
361}
362
363inline std::vector<cell_type_descriptor_t>
365 std::vector<cell_type_descriptor_t> out;
366 for (const auto& d : by_kind_ | std::views::values) {
367 if (d->category() == c) {
368 out.push_back(d);
369 }
370 }
371 return out;
372}
373
374inline std::vector<cell_type_descriptor_t>
375ParcelRegistry::find_by_storage(const std::type_index ti) const {
376 std::vector<cell_type_descriptor_t> out;
377 for (const auto& d : by_kind_ | std::views::values) {
378 if (d->storage_type() == ti) {
379 out.push_back(d);
380 }
381 }
382 return out;
383}
384
385inline Definition ParcelRegistry::define(const std::string_view kind) const {
386 const auto root = find(kind);
387 if (root == nullptr) {
389 "ParcelRegistry::define: unknown kind '" + std::string(kind) + "'", std::string(kind));
390 }
391 Definition out{root, {}};
392 std::vector<std::string_view> queue;
393
394 auto enqueue_subs = [&](cell_type_descriptor_t const& d) {
395 if (auto const* st = dynamic_cast<ISubTypes const*>(d.get()); st != nullptr) {
396 for (auto sk : st->sub_kinds()) {
397 queue.push_back(sk);
398 }
399 }
400 };
401
402 enqueue_subs(root);
403 while (!queue.empty()) {
404 auto k = queue.back();
405 queue.pop_back();
406 if (k == root->kind()) {
407 continue;
408 }
409 if (out.referenced.contains(k)) {
410 continue;
411 }
412 auto d = find(k);
413 if (d == nullptr) {
415 "ParcelRegistry::define: kind '" + std::string(root->kind()) +
416 "' references unregistered kind '" + std::string(k) + "'",
417 std::string(k));
418 }
419 out.referenced.emplace(std::string(k), d);
420 enqueue_subs(d);
421 }
422 return out;
423}
424
425} // 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
JSON shape was wrong (missing/non-string k, missing v, etc.).
Definition error.h:158
Runtime catalog of cell-type descriptors, keyed by wire kind id.
Definition registry.h:115
Definition define(std::string_view kind) const
Build a Definition rooted at kind plus everything it references.
Definition registry.h:385
std::vector< cell_type_descriptor_t > all() const
Every registered descriptor.
Definition registry.h:337
void register_kind(cell_type_descriptor_t d)
Register or replace a descriptor by its kind id.
Definition registry.h:252
ParcelRegistry & register_kinds(Ds &&... ds)
Variadic shorthand: register many descriptors in one call.
Definition registry.h:144
std::size_t count() const noexcept
Number of registered kinds.
Definition registry.h:355
std::vector< std::string_view > kinds() const
Every registered kind id.
Definition registry.h:346
bool contains(std::string_view kind) const
Whether kind is registered.
Definition registry.h:359
std::vector< cell_type_descriptor_t > find_by_category(descriptor::CellCategory c) const
Every descriptor whose category() matches c.
Definition registry.h:364
cell_type_descriptor_t find(std::string_view kind) const
Look up a descriptor by kind id.
Definition registry.h:259
cell_t cell_from_json(json_t const &j) const
Deserialize any registered cell from JSON, dispatching by "k".
Definition registry.h:266
ParcelRegistry & register_cells()
Variadic shorthand: register one descriptor per cell type.
Definition registry.h:160
std::vector< cell_type_descriptor_t > find_by_storage() const
Every descriptor whose storage type is T.
Definition registry.h:238
Registry was asked to dispatch a kind it does not know.
Definition error.h:175
Runtime cell-type descriptors and the schema-graph mix-ins (IHasFields, ISubTypes).
CellCategory
Coarse runtime classification of a cell type.
Definition descriptor.h:35
ParcelError and the non-throwing surface that returns std::expected.
nlohmann::json typedef shared across cell types.
nlohmann::json json_t
Project-wide alias for nlohmann::json.
Definition json.h:19
Toggles for the built-in cell registrations performed by register_builtins.
Definition registry.h:50
bool ulid
Register UlidCell (only takes effect when the ULID dependency is enabled — PARCEL_HAS_ULID; an inert ...
Definition registry.h:75
bool commons
Register the commons cells: ColorCell, IconCell, DisplayInfoCell, FlagCell, FlagSetCell,...
Definition registry.h:70
bool typed_collections
Register TypedListCell<P>, TypedMapCell<P>, and TypedHashMapCell<P> for every primitive P.
Definition registry.h:59
bool collections
Register ListCell, MapCell, and HashMapCell (heterogeneous).
Definition registry.h:54
bool primitives
Register every PrimitiveCell<T>.
Definition registry.h:52
bool std
Register the std-adapter cells: SystemTimePointCell, UnixMillisCell, DurationMsCell,...
Definition registry.h:64
Closed-over schema for a single root kind plus everything it references.
Definition registry.h:84
json_t to_json() const
Serialize to a JSON object with root and referenced blocks.
Definition registry.h:94
std::map< std::string, cell_type_descriptor_t, std::less<> > referenced
Every kind transitively referenced by root, keyed by kind id.
Definition registry.h:88
cell_type_descriptor_t root
Descriptor for the requested root kind.
Definition registry.h:86
static constexpr std::string_view KEY_KIND
JSON key for the kind id ("k").
Definition cell.h:86
Mix-in declaring a descriptor refers to other registered cell kinds.
Definition descriptor.h:148
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.
@ InvalidJson
Input was not parseable JSON or had wrong shape.
@ UnknownKind
JSON "k" referenced a kind not registered.