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}