Skip to main content

async_nodes/
registry.rs

1//! The node & condition registry: the engine/definition separation made concrete.
2//!
3//! Consumers register their node types (as factories), named routing predicates, and
4//! mergers; the engine instantiates a [`GraphDef`](super::def::GraphDef) against the
5//! registry. A GUI can then load, edit, save, and *instantiate* graphs — the full n8n
6//! loop. Registries are per-`S`: consumers with multiple state types build one registry
7//! per state type.
8//!
9//! Node config schemas are consumer-owned and consumer-versioned (e.g. version inside the
10//! config blob as `{"v": 2, ...}`); the engine stays out of it.
11
12use std::{collections::HashMap, sync::Arc};
13
14use super::{
15    BspContext, condition::ConditionFn, def::NodeDef, error::BspError, merge::Merger,
16    node::BspNode, value::BspValue,
17};
18
19/// Factory building a node instance from its definition (config blob included) and the
20/// dependency-injection context.
21pub type NodeFactory<S> =
22    Box<dyn Fn(&NodeDef, &BspContext) -> Result<Box<dyn BspNode<S>>, BspError> + Send + Sync>;
23
24/// Converts a JSON static-input value from a [`GraphDef`](super::def::GraphDef) into a
25/// typed [`BspValue`](super::value::BspValue) at instantiation time.
26pub type ValueFactory = Arc<dyn Fn(&serde_json::Value) -> BspValue + Send + Sync>;
27
28/// The default static-input mapping: JSON bools/numbers/strings become *typed* values
29/// (so dataflow nodes downcast them exactly as if `set_static_input` had been called with
30/// the Rust value), and anything else arrives as a wrapped `serde_json::Value`.
31///
32/// Consumers wanting different typing (e.g. the snarl bridge, which historically injects
33/// `bool`/`i64`/`f64`/`String` from property JSON) override this with
34/// [`NodeRegistry::register_value_factory`].
35pub fn default_value_factory(value: &serde_json::Value) -> BspValue {
36    match value {
37        serde_json::Value::Bool(b) => BspValue::new(*b),
38        serde_json::Value::Number(n) => {
39            if let Some(i) = n.as_i64() {
40                BspValue::new(i)
41            } else if let Some(u) = n.as_u64() {
42                BspValue::new(u)
43            } else {
44                BspValue::new(n.as_f64().unwrap_or_default())
45            }
46        }
47        serde_json::Value::String(s) => BspValue::new(s.clone()),
48        other => BspValue::new(other.clone()),
49    }
50}
51
52/// A per-`S` registry of node factories, named conditions, and mergers.
53///
54/// Registration is fail-fast at graph-build time: unknown `type_name`s, condition names,
55/// or merger names are reported by [`BspGraph::from_def`](super::graph::BspGraph::from_def)
56/// as a [`BspError::ValidationError`] listing every unknown name.
57pub struct NodeRegistry<S = ()> {
58    /// Node type name -> factory.
59    pub(crate) factories: HashMap<String, NodeFactory<S>>,
60    /// Condition name -> predicate (also the re-registration point for `Custom` labels).
61    pub(crate) conditions: HashMap<String, ConditionFn<S>>,
62    /// Merger name -> merger.
63    pub(crate) mergers: HashMap<String, Arc<dyn Merger<S>>>,
64    /// JSON static-input value -> typed `BspValue` (used by `from_def`).
65    pub(crate) value_factory: ValueFactory,
66}
67
68impl<S> Default for NodeRegistry<S> {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl<S> NodeRegistry<S> {
75    /// Instantiates an empty registry.
76    #[must_use]
77    pub fn new() -> Self {
78        Self {
79            factories: HashMap::new(),
80            conditions: HashMap::new(),
81            mergers: HashMap::new(),
82            value_factory: Arc::new(default_value_factory),
83        }
84    }
85
86    /// Registers a node type factory under its `type_name`.
87    pub fn register_node(
88        &mut self,
89        type_name: impl Into<String>,
90        factory: impl Fn(&NodeDef, &BspContext) -> Result<Box<dyn BspNode<S>>, BspError>
91        + Send
92        + Sync
93        + 'static,
94    ) {
95        self.factories.insert(type_name.into(), Box::new(factory));
96    }
97
98    /// Registers a named condition predicate.
99    pub fn register_condition(
100        &mut self,
101        name: impl Into<String>,
102        f: impl Fn(&S, &super::run::ValueStore) -> bool + Send + Sync + 'static,
103    ) {
104        self.conditions.insert(name.into(), Arc::new(f));
105    }
106
107    /// Registers a named merger.
108    pub fn register_merger<M: Merger<S> + 'static>(&mut self, name: impl Into<String>, merger: M) {
109        self.mergers.insert(name.into(), Arc::new(merger));
110    }
111
112    /// Overrides how [`GraphDef`](super::def::GraphDef) static-input JSON becomes typed
113    /// pin values at instantiation.
114    ///
115    /// The default ([`default_value_factory`]) produces typed `bool` / `i64` / `u64` /
116    /// `f64` / `String` values. Override this when your nodes expect different types —
117    /// e.g. everything as raw `serde_json::Value`:
118    /// `registry.register_value_factory(|v| BspValue::new(v.clone()))`.
119    pub fn register_value_factory(
120        &mut self,
121        factory: impl Fn(&serde_json::Value) -> BspValue + Send + Sync + 'static,
122    ) {
123        self.value_factory = Arc::new(factory);
124    }
125
126    /// Returns whether a node type is registered.
127    #[must_use]
128    pub fn contains_node(&self, type_name: &str) -> bool {
129        self.factories.contains_key(type_name)
130    }
131
132    /// Returns whether a condition is registered.
133    #[must_use]
134    pub fn contains_condition(&self, name: &str) -> bool {
135        self.conditions.contains_key(name)
136    }
137
138    /// Returns whether a merger is registered.
139    #[must_use]
140    pub fn contains_merger(&self, name: &str) -> bool {
141        self.mergers.contains_key(name)
142    }
143
144    /// The registered node type names (the GUI palette catalog).
145    #[must_use]
146    pub fn node_type_names(&self) -> Vec<&str> {
147        self.factories.keys().map(String::as_str).collect()
148    }
149
150    #[must_use]
151    fn with_mut(mut self, f: impl FnOnce(&mut Self)) -> Self {
152        f(&mut self);
153        self
154    }
155
156    /// Builder variant of [`NodeRegistry::register_node`].
157    #[must_use]
158    pub fn with_node(
159        self,
160        type_name: impl Into<String>,
161        factory: impl Fn(&NodeDef, &BspContext) -> Result<Box<dyn BspNode<S>>, BspError>
162        + Send
163        + Sync
164        + 'static,
165    ) -> Self {
166        self.with_mut(|r| r.register_node(type_name, factory))
167    }
168
169    /// Builder variant of [`NodeRegistry::register_condition`].
170    #[must_use]
171    pub fn with_condition(
172        self,
173        name: impl Into<String>,
174        f: impl Fn(&S, &super::run::ValueStore) -> bool + Send + Sync + 'static,
175    ) -> Self {
176        self.with_mut(|r| r.register_condition(name, f))
177    }
178
179    /// Builder variant of [`NodeRegistry::register_merger`].
180    #[must_use]
181    pub fn with_merger<M: Merger<S> + 'static>(self, name: impl Into<String>, merger: M) -> Self {
182        self.with_mut(|r| r.register_merger(name, merger))
183    }
184
185    /// Builder variant of [`NodeRegistry::register_value_factory`].
186    #[must_use]
187    pub fn with_value_factory(
188        self,
189        factory: impl Fn(&serde_json::Value) -> BspValue + Send + Sync + 'static,
190    ) -> Self {
191        self.with_mut(|r| r.register_value_factory(factory))
192    }
193}