Skip to main content

beetry_plugin/
node.rs

1use std::{collections::BTreeMap, marker::PhantomData};
2
3use anyhow::Result;
4use beetry_channel::{AnyBoxReceiver, AnyBoxSender};
5use beetry_core::{BoxActionBehavior, BoxConditionBehavior, BoxNode, NonEmptyNodes};
6use beetry_editor_types::{
7    output::node::{ParameterValue, Parameters},
8    spec::node::{NodeSpec, ParamsSpec, PortKey},
9};
10use bon::Builder;
11use serde::Deserialize;
12
13use crate::{BoxPlugin, ConstructPlugin, Named, PluginConstructor, PluginError, unique_plugins};
14
15pub type LeafReconstructionData = NodeReconstructionData<LeafContext>;
16pub type ActionReconstructionData = LeafReconstructionData;
17pub type ConditionReconstructionData = LeafReconstructionData;
18pub type ControlReconstructionData = NodeReconstructionData<ControlContext>;
19pub type DecoratorReconstructionData = NodeReconstructionData<DecoratorContext>;
20
21#[derive(Debug, Builder)]
22pub struct NodeReconstructionData<C> {
23    pub context: C,
24    #[builder(default)]
25    pub parameters: Parameters,
26}
27
28#[derive(Debug, Default, Builder)]
29pub struct LeafContext {
30    #[builder(default)]
31    pub receivers: BTreeMap<PortKey, AnyBoxReceiver>,
32    #[builder(default)]
33    pub senders: BTreeMap<PortKey, AnyBoxSender>,
34}
35
36impl LeafContext {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn take_receiver(&mut self, key: &PortKey) -> Result<AnyBoxReceiver> {
42        self.receivers
43            .remove(key)
44            .ok_or_else(|| anyhow::anyhow!("failed to obtain receiver for port '{}'", key.as_str()))
45    }
46
47    pub fn take_sender(&mut self, key: &PortKey) -> Result<AnyBoxSender> {
48        self.senders
49            .remove(key)
50            .ok_or_else(|| anyhow::anyhow!("failed to obtain sender for port '{}'", key.as_str()))
51    }
52}
53
54pub struct ControlContext {
55    pub children: NonEmptyNodes,
56}
57
58impl ControlContext {
59    pub fn new(children: NonEmptyNodes) -> Self {
60        Self { children }
61    }
62}
63
64pub struct DecoratorContext {
65    pub child: BoxNode,
66}
67
68impl DecoratorContext {
69    pub fn new(child: BoxNode) -> Self {
70        Self { child }
71    }
72}
73
74/// Provides parameter specification for a parameter type.
75///
76/// Implement this trait for a serializable params struct to describe which
77/// fields can be configured in the editor and how they should be validated.
78///
79/// ```rust, no_run
80/// # use beetry_editor_types::spec::node::{
81/// #     FieldDefinition, FieldMetadata, FieldTypeSpec, ParamsSpec,
82/// # };
83/// # use beetry_plugin::ProvideParamSpec;
84/// # use mitsein::iter1::IntoIterator1;
85/// use serde::Deserialize;
86///
87/// /// Not strictly required to implement `ProvideParamSpec` but param type
88/// /// must be deserializable to be usable with any node registration macro.
89/// /// See the plugin chapter in the book for an in-depth explanation:
90/// /// <https://beetry.pages.dev/plugins/plugin.html>.
91/// #[derive(Deserialize)]
92/// struct RetryParams;
93///
94/// impl ProvideParamSpec for RetryParams {
95///     fn provide() -> ParamsSpec {
96///         [(
97///             "retry_limit".into(),
98///             FieldDefinition {
99///                 type_spec: FieldTypeSpec::U64(FieldMetadata::default()),
100///                 description: Some(
101///                     "Maximum number of retry attempts".into(),
102///                 ),
103///             },
104///         )]
105///         .into_iter1()
106///         .collect1()
107///     }
108/// }
109/// ```
110pub trait ProvideParamSpec {
111    fn provide() -> ParamsSpec;
112}
113
114pub struct ParamsDeserializer;
115
116impl ParamsDeserializer {
117    pub fn deserialize<T>(params: Parameters) -> Result<T>
118    where
119        T: for<'de> Deserialize<'de>,
120    {
121        let deserializer = serde_value::ValueDeserializer::<serde_value::DeserializerError>::new(
122            serde_value::Value::Map(
123                params
124                    .into_iter()
125                    .map(|(name, value)| {
126                        let value = match value {
127                            ParameterValue::Bool(b) => serde_value::Value::Bool(b),
128                            ParameterValue::U16(u) => serde_value::Value::U16(u),
129                            ParameterValue::U64(u) => serde_value::Value::U64(u),
130                            ParameterValue::I64(i) => serde_value::Value::I64(i),
131                            ParameterValue::F64(f) => serde_value::Value::F64(f),
132                            ParameterValue::String(s) => serde_value::Value::String(s),
133                        };
134                        (serde_value::Value::String(name), value)
135                    })
136                    .collect::<BTreeMap<_, _>>(),
137            ),
138        );
139        Ok(T::deserialize(deserializer)?)
140    }
141}
142
143type BoxActionFactoryFn = Box<dyn Fn(ActionReconstructionData) -> Result<BoxActionBehavior>>;
144
145pub type ActionFactory = Factory<BoxActionFactoryFn, ActionReconstructionData, BoxActionBehavior>;
146pub type ConditionFactory =
147    Factory<BoxConditionFactoryFn, ConditionReconstructionData, BoxConditionBehavior>;
148type BoxConditionFactoryFn =
149    Box<dyn Fn(ConditionReconstructionData) -> Result<BoxConditionBehavior>>;
150
151type BoxControlFactoryFn = Box<dyn Fn(ControlReconstructionData) -> Result<BoxNode>>;
152pub type ControlFactory = Factory<BoxControlFactoryFn, ControlReconstructionData, BoxNode>;
153type BoxDecoratorFactoryFn = Box<dyn Fn(DecoratorReconstructionData) -> Result<BoxNode>>;
154pub type DecoratorFactory = Factory<BoxDecoratorFactoryFn, DecoratorReconstructionData, BoxNode>;
155
156pub struct Factory<F, I, O> {
157    func: F,
158    _ph1: PhantomData<I>,
159    _ph2: PhantomData<O>,
160}
161
162impl<F, I, O> Factory<F, I, O>
163// I: Input
164// O: Output
165where
166    F: Fn(I) -> Result<O>,
167{
168    pub fn new(func: F) -> Self {
169        Self {
170            func,
171            _ph1: PhantomData,
172            _ph2: PhantomData,
173        }
174    }
175
176    pub fn try_create(&self, data: I) -> Result<O> {
177        (self.func)(data)
178    }
179}
180pub type BoxActionPlugin = BoxPlugin<NodeSpec, ActionFactory>;
181pub type BoxConditionPlugin = BoxPlugin<NodeSpec, ConditionFactory>;
182pub type BoxControlPlugin = BoxPlugin<NodeSpec, ControlFactory>;
183pub type BoxDecoratorPlugin = BoxPlugin<NodeSpec, DecoratorFactory>;
184
185impl Named for NodeSpec {
186    fn name(&self) -> &str {
187        &self.name().0
188    }
189}
190
191pub type ActionPluginConstructor = PluginConstructor<NodeSpec, ActionFactory>;
192pub type ConditionPluginConstructor = PluginConstructor<NodeSpec, ConditionFactory>;
193pub type ControlPluginConstructor = PluginConstructor<NodeSpec, ControlFactory>;
194pub type DecoratorPluginConstructor = PluginConstructor<NodeSpec, DecoratorFactory>;
195
196impl ActionPluginConstructor {
197    pub fn plugins() -> Result<Vec<BoxActionPlugin>, PluginError> {
198        unique_plugins::<Self, <Self as ConstructPlugin>::Spec, <Self as ConstructPlugin>::Factory>(
199        )
200    }
201}
202
203impl ConditionPluginConstructor {
204    pub fn plugins() -> Result<Vec<BoxConditionPlugin>, PluginError> {
205        unique_plugins::<Self, <Self as ConstructPlugin>::Spec, <Self as ConstructPlugin>::Factory>(
206        )
207    }
208}
209
210impl ControlPluginConstructor {
211    pub fn plugins() -> Result<Vec<BoxControlPlugin>, PluginError> {
212        unique_plugins::<Self, <Self as ConstructPlugin>::Spec, <Self as ConstructPlugin>::Factory>(
213        )
214    }
215}
216
217impl DecoratorPluginConstructor {
218    pub fn plugins() -> Result<Vec<BoxDecoratorPlugin>, PluginError> {
219        unique_plugins::<Self, <Self as ConstructPlugin>::Spec, <Self as ConstructPlugin>::Factory>(
220        )
221    }
222}
223
224inventory::collect! {ActionPluginConstructor}
225inventory::collect! {ConditionPluginConstructor}
226inventory::collect! {ControlPluginConstructor}
227inventory::collect! {DecoratorPluginConstructor}
228
229#[cfg(test)]
230mod tests {
231    use beetry_editor_types::spec::node::{NodeKind, NodeName, NodeSpec, NodeSpecKey};
232
233    use super::*;
234    use crate::Plugin;
235
236    struct TestPluginA {
237        spec: NodeSpec,
238        factory: ActionFactory,
239    }
240
241    impl Plugin for TestPluginA {
242        type Spec = NodeSpec;
243        type Factory = ActionFactory;
244
245        fn new() -> Self {
246            Self {
247                spec: NodeSpec::builder()
248                    .key(NodeSpecKey::new(
249                        NodeName::new("TestPlugin"),
250                        NodeKind::action(),
251                    ))
252                    .build(),
253                factory: ActionFactory::new(Box::new(|_| {
254                    Err(anyhow::anyhow!("This is a test factory, not functional"))
255                })),
256            }
257        }
258
259        fn spec(&self) -> &Self::Spec {
260            &self.spec
261        }
262
263        fn factory(&self) -> &Self::Factory {
264            &self.factory
265        }
266
267        fn into_parts(self: Box<Self>) -> (Self::Spec, Self::Factory) {
268            (self.spec, self.factory)
269        }
270    }
271
272    struct TestPluginB {
273        spec: NodeSpec,
274        factory: ActionFactory,
275    }
276
277    impl Plugin for TestPluginB {
278        type Spec = NodeSpec;
279        type Factory = ActionFactory;
280
281        fn new() -> Self {
282            Self {
283                spec: NodeSpec::builder()
284                    .key(NodeSpecKey::new(
285                        NodeName::new("TestPlugin"),
286                        NodeKind::action(),
287                    ))
288                    .build(),
289                factory: ActionFactory::new(Box::new(|_| {
290                    Err(anyhow::anyhow!("This is a test factory, not functional"))
291                })),
292            }
293        }
294
295        fn spec(&self) -> &Self::Spec {
296            &self.spec
297        }
298
299        fn factory(&self) -> &Self::Factory {
300            &self.factory
301        }
302
303        fn into_parts(self: Box<Self>) -> (Self::Spec, Self::Factory) {
304            (self.spec, self.factory)
305        }
306    }
307
308    inventory::submit! {
309        ActionPluginConstructor::new::<TestPluginA>()
310    }
311
312    //@todo registering duplicated entry might affect other tests when plugins()
313    //@todo method is called.
314    //Better to avoid global registration if possible
315    inventory::submit! {
316        ActionPluginConstructor::new::<TestPluginB>()
317    }
318
319    #[test]
320    fn duplicate_plugin_name_error() {
321        let result = ActionPluginConstructor::plugins();
322        assert!(matches!(
323            result,
324            Err(PluginError::DuplicateName(name)) if name == NodeName::new("TestPlugin").0
325        ));
326    }
327}