Skip to main content

node_flow/context/storage/local_storage/
design.rs

1/// Provides type-based local storage for arbitrary values.
2///
3/// `LocalStorage` is a type-based per branch local storage,
4/// which is not be shared with any other branch.
5/// It allows storing and retrieving values by their type.
6/// Each type `T` has at most **one instance** stored at a time.
7///
8/// This trait is designed for use in systems that need to manage
9/// per-node state which should be shared between nodes in the same branch,
10/// but not between branches.
11///
12/// # Examples
13/// ```
14/// use node_flow::context::storage::LocalStorage;
15/// use std::collections::HashMap;
16/// use std::any::{TypeId, Any};
17///
18/// struct ExampleStorage(HashMap<TypeId, Box<dyn Any>>);
19///
20/// impl LocalStorage for ExampleStorage {
21///     fn get<T>(&self) -> Option<&T>
22///     where
23///         T: 'static,
24///     {
25///         self.0.get(&TypeId::of::<T>())?
26///             .downcast_ref::<T>()
27///     }
28///
29///     fn get_mut<T>(&mut self) -> Option<&mut T>
30///     where
31///         T: 'static,
32///     {
33///         self.0.get_mut(&TypeId::of::<T>())?
34///             .downcast_mut::<T>()
35///     }
36///
37///     fn insert<T>(&mut self, val: T) -> Option<T>
38///     where
39///         T: Clone + Send + 'static,
40///     {
41///         self.0
42///             .insert(TypeId::of::<T>(), Box::new(val))
43///             .and_then(|boxed| boxed.downcast::<T>().ok().map(|b| *b))
44///     }
45///
46///     fn remove<T>(&mut self) -> Option<T>
47///     where
48///         T: 'static,
49///     {
50///         self.0
51///             .remove(&TypeId::of::<T>())
52///             .and_then(|boxed| boxed.downcast::<T>().ok().map(|b| *b))
53///     }
54/// }
55/// ```
56pub trait LocalStorage {
57    /// Gets reference of a value with type `T` from storage if it is present.
58    ///
59    /// # Examples
60    /// ```
61    /// # use node_flow::context::storage::{LocalStorage, local_storage::{Merge, MergeResult, LocalStorageImpl}};
62    /// # type ExampleStorage = LocalStorageImpl;
63    /// #[derive(Debug, PartialEq, Eq, Clone)]
64    /// struct ExampleValue(u8);
65    /// impl Merge for ExampleValue // ...
66    /// # {
67    /// #     fn merge(parent: Option<&Self>, others: Box<[Self]>) -> MergeResult<Self> { todo!() }
68    /// # }
69    /// let mut storage = ExampleStorage::new();
70    ///
71    /// storage.insert(ExampleValue(5u8));
72    /// let result: Option<&ExampleValue> = storage.get();
73    /// assert_eq!(result, Some(&ExampleValue(5u8)));
74    /// let result: Option<&u16> = storage.get();
75    /// assert_eq!(result, None);
76    /// ```
77    fn get<T>(&self) -> Option<&T>
78    where
79        T: 'static;
80
81    /// Gets mutable reference of a value with type `T` from storage if it is present.
82    ///
83    /// # Examples
84    /// ```
85    /// # use node_flow::context::storage::{LocalStorage, local_storage::{Merge, MergeResult, LocalStorageImpl}};
86    /// # type ExampleStorage = LocalStorageImpl;
87    /// #[derive(Debug, PartialEq, Eq, Clone)]
88    /// struct ExampleValue(u8);
89    /// impl Merge for ExampleValue // ...
90    /// # {
91    /// #     fn merge(parent: Option<&Self>, others: Box<[Self]>) -> MergeResult<Self> { todo!() }
92    /// # }
93    /// let mut storage = ExampleStorage::new();
94    ///
95    /// storage.insert(ExampleValue(5u8));
96    /// if let Some(val) = storage.get_mut::<ExampleValue>() {
97    ///     val.0 = 15u8;
98    /// }
99    /// let result: Option<&ExampleValue> = storage.get();
100    /// assert_eq!(result, Some(&ExampleValue(15u8)));
101    /// ```
102    fn get_mut<T>(&mut self) -> Option<&mut T>
103    where
104        T: 'static;
105
106    /// Inserts value with type `T` to storage and returns the value that was there previously if it was there.
107    ///
108    /// # Examples
109    /// ```
110    /// # use node_flow::context::storage::{LocalStorage, local_storage::{Merge, MergeResult, LocalStorageImpl}};
111    /// # type ExampleStorage = LocalStorageImpl;
112    /// #[derive(Debug, PartialEq, Eq, Clone)]
113    /// struct ExampleValue(u8);
114    /// impl Merge for ExampleValue // ...
115    /// # {
116    /// #     fn merge(parent: Option<&Self>, others: Box<[Self]>) -> MergeResult<Self> { todo!() }
117    /// # }
118    /// let mut storage = ExampleStorage::new();
119    ///
120    /// let result = storage.insert(ExampleValue(5u8));
121    /// assert_eq!(result, None);
122    /// storage.insert(ExampleValue(15u8));
123    /// let result = storage.insert(ExampleValue(25u8));
124    /// assert_eq!(result, Some(ExampleValue(15u8)));
125    /// let result: Option<&ExampleValue> = storage.get();
126    /// assert_eq!(result, Some(&ExampleValue(25u8)));
127    /// ```
128    fn insert<T>(&mut self, val: T) -> Option<T>
129    where
130        T: Merge + Clone + Send + 'static;
131
132    /// Removes and returns value with type `T` from storage if it is present.
133    ///
134    /// # Examples
135    /// ```
136    /// # use node_flow::context::storage::{LocalStorage, local_storage::{Merge, MergeResult, LocalStorageImpl}};
137    /// # type ExampleStorage = LocalStorageImpl;
138    /// #[derive(Debug, PartialEq, Eq, Clone)]
139    /// struct ExampleValue(u8);
140    /// impl Merge for ExampleValue // ...
141    /// # {
142    /// #     fn merge(parent: Option<&Self>, others: Box<[Self]>) -> MergeResult<Self> { todo!() }
143    /// # }
144    /// let mut storage = ExampleStorage::new();
145    ///
146    /// let result = storage.insert(ExampleValue(5u8));
147    /// assert_eq!(result, None);
148    /// let result = storage.remove();
149    /// assert_eq!(result, Some(ExampleValue(5u8)));
150    /// let result = storage.remove::<ExampleValue>();
151    /// assert_eq!(result, None);
152    /// ```
153    fn remove<T>(&mut self) -> Option<T>
154    where
155        T: 'static;
156}
157
158/// Represents the result of merging multiple instances of a type during context merging.
159///
160/// This enum is used by the [`Merge`] trait to determine how merging should affect the parent value.
161/// It is up to the implementor to decide whether to keep, replace, or remove it entirely.
162///
163/// See also [`Merge`] trait.
164#[derive(Debug)]
165pub enum MergeResult<T> {
166    /// Keep the existing parent value as-is, whether it exists or not.
167    KeepParent,
168    /// Replace the parent value or insert this value if it does not exist.
169    ReplaceOrInsert(T),
170    /// Remove the value from the parent context entirely.
171    Remove,
172}
173
174/// Defines how multiple instances of a type are merged.
175///
176/// The `Merge` trait is used to combine several versions of a value into a single instance.
177/// It is mainly used in a fork-join lifecycle.
178///
179/// Implementations should define the merging logic between an optional parent
180/// value and a collection of child values.
181///
182/// # Examples
183/// ```
184/// use node_flow::context::storage::local_storage::{Merge, MergeResult};
185///
186/// struct Counter(u32);
187///
188/// impl Merge for Counter {
189///     fn merge(parent: Option<&Self>, others: Box<[Self]>) -> MergeResult<Self> {
190///         let sum: u32 = others.iter().map(|c| c.0).sum();
191///         let base = parent.map_or(0, |p| p.0);
192///         MergeResult::ReplaceOrInsert(Counter(base + sum))
193///     }
194/// }
195/// ```
196pub trait Merge: Sized {
197    /// Merges the parent value with a list of child values and returns a [`MergeResult`].
198    ///
199    /// # Parameters
200    /// - `parent`: An optional reference to the existing value in the parent context.
201    /// - `others`: A list of values to merge into the parent.
202    ///
203    /// # Returns
204    /// A [`MergeResult`] indicating how the parent should be updated.
205    fn merge(parent: Option<&Self>, others: Box<[Self]>) -> MergeResult<Self>;
206}