Skip to main content

gen_platform/
catalog.rs

1//! `DispatcherCatalog` — inventory-style fleet-wide registry of
2//! every typed dispatcher in scope. Any pleme-io crate that
3//! `#[derive(TypedDispatcher)]`s an enum can register a catalog
4//! entry via the [`register_dispatcher!`](crate::register_dispatcher)
5//! macro; gen-platform iterates them all at runtime through
6//! `DispatcherCatalog::registered()`.
7//!
8//! This is the catalog reflection layer per `theory/QUIRK-APPLIER.md`
9//! §IV-bis.3.e — a single queryable surface for *every place in
10//! pleme-io that switches on a typed tag*. Operators query it via
11//! `gen dispatchers catalog`; substrate emitters consume it to
12//! produce Nix helpers skeletons + Lisp catalog entries
13//! mechanically.
14
15/// One entry in the dispatcher catalog. Materialized via
16/// [`register_dispatcher!`](crate::register_dispatcher) — the
17/// macro emits an `inventory::submit!` block per registered
18/// dispatcher.
19pub struct DispatcherEntry {
20    /// Caller-supplied label (e.g. `"gen.cargo.crate-quirk"`,
21    /// `"shinka.migration"`, `"saguao.permission"`). Dot-separated
22    /// fleet-wide-unique identifier. Used by operator queries +
23    /// substrate emitters.
24    pub label: &'static str,
25    /// Function returning the variant universe (kebab-case serde
26    /// tags). Indirected as a fn pointer so the catalog stays
27    /// `'static` while the underlying enum can live anywhere.
28    pub variant_kinds: fn() -> Vec<&'static str>,
29    /// Function returning per-variant field names. Mirror of
30    /// `TypedDispatcher::variant_fields`.
31    pub variant_fields: fn() -> Vec<(&'static str, Vec<&'static str>)>,
32    /// Function returning the variant count.
33    pub variant_count: fn() -> usize,
34}
35
36inventory::collect!(DispatcherEntry);
37
38/// Iterate every catalog entry registered fleet-wide. Order is
39/// inventory-dependent (not deterministic) — sort by `label` if
40/// the consumer needs stable iteration.
41#[must_use]
42pub fn registered() -> Vec<&'static DispatcherEntry> {
43    inventory::iter::<DispatcherEntry>().collect()
44}
45
46/// Look up a catalog entry by its dot-separated label.
47#[must_use]
48pub fn by_label(label: &str) -> Option<&'static DispatcherEntry> {
49    inventory::iter::<DispatcherEntry>().find(|e| e.label == label)
50}
51
52/// Register a `#[derive(TypedDispatcher)]` enum into the fleet-wide
53/// dispatcher catalog. Place at module scope; one call per enum.
54///
55/// ```ignore
56/// use gen_platform::register_dispatcher;
57///
58/// #[derive(serde::Serialize, gen_platform::TypedDispatcher)]
59/// #[serde(tag = "kind", rename_all = "kebab-case")]
60/// enum MyMigration { AddColumn { table: String }, DropIndex { name: String } }
61///
62/// register_dispatcher!("shinka.migration", MyMigration);
63/// ```
64///
65/// After registration, `gen dispatchers catalog` lists the entry
66/// + substrate emitters can produce the matching Nix dispatch
67/// skeleton without naming `MyMigration` directly.
68#[macro_export]
69macro_rules! register_dispatcher {
70    ($label:expr, $enum_ty:ty) => {
71        $crate::catalog::__inventory::submit! {
72            $crate::catalog::DispatcherEntry {
73                label: $label,
74                variant_kinds: || {
75                    <$enum_ty as $crate::TypedDispatcherTrait>::variant_kinds()
76                },
77                variant_fields: || {
78                    <$enum_ty as $crate::TypedDispatcherTrait>::variant_fields()
79                },
80                variant_count: || {
81                    <$enum_ty as $crate::TypedDispatcherTrait>::variant_count()
82                },
83            }
84        }
85    };
86}
87
88/// Public re-export of the inventory crate for the
89/// `register_dispatcher!` macro to use. Consumers don't depend on
90/// inventory directly — gen-platform owns the dependency edge.
91#[doc(hidden)]
92pub use ::inventory as __inventory;