daedalus_nodes/
lib.rs

1//! Built-in nodes and bundles. See `PLAN.md` for staged work.
2//! Bundles are feature-gated; registration order is deterministic.
3//!
4/// Re-export of the `#[node]` macro for convenience.
5pub use daedalus_macros::node;
6pub type NodeDescriptor = daedalus_registry::store::NodeDescriptor;
7
8#[cfg(feature = "bundle-starter")]
9mod bundle_starter;
10
11#[cfg(feature = "bundle-utils")]
12mod bundle_utils;
13
14#[cfg(feature = "registry-adapter")]
15pub mod registry_adapter;
16
17#[cfg(feature = "planner-adapter")]
18pub mod planner_adapter;
19
20pub mod bundle_demo;
21extern crate self as daedalus_nodes;
22pub mod __macro_support {
23    pub use daedalus_runtime::plugins::{NodeInstall, Plugin, PluginRegistry};
24}
25
26/// Declare a plugin struct that installs a set of node descriptors and handlers
27/// in one shot. Each entry should correspond to a `#[node]`-annotated
28/// function name in scope so `<name>_descriptor` and `<name>_handler` exist.
29///
30/// ```ignore
31/// use daedalus_nodes::declare_plugin;
32/// use daedalus_macros::node;
33///
34/// #[node(id = "demo:noop", inputs("in"), outputs("out"))]
35/// fn noop(value: i64) -> Result<i64, daedalus_runtime::NodeError> {
36///     Ok(value)
37/// }
38///
39/// declare_plugin!(DemoPlugin, "demo", [noop]);
40/// ```
41#[macro_export]
42macro_rules! declare_plugin {
43    // Basic form (no hook).
44    ($plugin:ident, $id:expr, [ $( $node:ident ),+ $(,)? ]) => {
45        paste::paste! {
46            #[derive(Clone, Debug)]
47            pub struct $plugin {
48                $(pub $node: [<$node Handle>]),+
49            }
50
51            impl $plugin {
52                pub fn new() -> Self {
53                    Self {
54                        $($node: $node::handle().with_prefix($id)),+
55                    }
56                }
57
58                $(
59                    pub fn [<node_ $node>](&self) -> [<$node Handle>] {
60                        $node::handle().with_prefix($id)
61                    }
62                )+
63            }
64
65            impl Default for $plugin {
66                fn default() -> Self {
67                    Self::new()
68                }
69            }
70
71            impl $plugin {
72                pub fn install(
73                    &self,
74                    registry: &mut $crate::__macro_support::PluginRegistry,
75                ) -> Result<(), &'static str> {
76                    $(
77                        registry.merge::<$node>()?;
78                    )+
79                    Ok(())
80                }
81            }
82
83            #[cfg(feature = "plugins")]
84            impl $crate::__macro_support::Plugin for $plugin {
85                fn id(&self) -> &'static str {
86                    $id
87                }
88
89                fn install(
90                    &self,
91                    registry: &mut $crate::__macro_support::PluginRegistry,
92                ) -> Result<(), &'static str> {
93                    self.install(registry)
94                }
95            }
96        }
97    };
98
99    // Form with an install hook: the block runs before node merges and can return
100    // an error. Binding name is provided by the caller.
101    ($plugin:ident, $id:expr, [ $( $node:ident ),+ $(,)? ], install = |$reg:ident| $body:block) => {
102        paste::paste! {
103            #[derive(Clone, Debug)]
104            pub struct $plugin {
105                $(pub $node: [<$node Handle>]),+
106            }
107
108            impl $plugin {
109                pub fn new() -> Self {
110                    Self {
111                        $($node: $node::handle().with_prefix($id)),+
112                    }
113                }
114
115                $(
116                    pub fn [<node_ $node>](&self) -> [<$node Handle>] {
117                        $node::handle().with_prefix($id)
118                    }
119                )+
120            }
121
122            impl Default for $plugin {
123                fn default() -> Self {
124                    Self::new()
125                }
126            }
127
128            impl $plugin {
129                pub fn install(
130                    &self,
131                    registry: &mut $crate::__macro_support::PluginRegistry,
132                ) -> Result<(), &'static str> {
133                    let $reg = registry;
134                    (|| -> Result<(), &'static str> { $body; Ok(()) })()?;
135                    $(
136                        $reg.merge::<$node>()?;
137                    )+
138                    Ok(())
139                }
140            }
141
142            #[cfg(feature = "plugins")]
143            impl $crate::__macro_support::Plugin for $plugin {
144                fn id(&self) -> &'static str {
145                    $id
146                }
147
148                fn install(
149                    &self,
150                    registry: &mut $crate::__macro_support::PluginRegistry,
151                ) -> Result<(), &'static str> {
152                    self.install(registry)
153                }
154            }
155        }
156    };
157}
158
159/// Register all enabled bundles, returning descriptors in deterministic order.
160///
161/// ```
162/// use daedalus_nodes::register_all;
163/// let nodes = register_all();
164/// let _ = nodes;
165/// ```
166pub fn register_all() -> Vec<NodeDescriptor> {
167    let mut nodes: Vec<NodeDescriptor> = Vec::new();
168    #[cfg(feature = "bundle-starter")]
169    {
170        nodes.extend(bundle_starter::nodes());
171    }
172    #[cfg(feature = "bundle-utils")]
173    {
174        nodes.extend(bundle_utils::nodes());
175    }
176    nodes.sort_by(|a, b| a.id.0.cmp(&b.id.0));
177    nodes
178}
179
180#[cfg(all(test, feature = "bundle-starter"))]
181mod tests {
182    use super::*;
183    use daedalus_core::compute::ComputeAffinity;
184
185    #[test]
186    fn deterministic_ordering() {
187        let nodes = register_all();
188        let mut sorted = nodes.clone();
189        sorted.sort_by(|a, b| a.id.0.cmp(&b.id.0));
190        assert_eq!(nodes, sorted);
191    }
192
193    #[test]
194    fn node_macro_attaches_metadata() {
195        let first = register_all()
196            .into_iter()
197            .find(|n| n.id.0 == "starter.print")
198            .expect("starter.print registered");
199        assert_eq!(first.default_compute, ComputeAffinity::CpuOnly);
200    }
201}