zrx_storage/storage/convert.rs
1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Storage conversions.
27
28use std::any::Any;
29use std::fmt::Debug;
30
31use zrx_store::{Key, Value};
32
33use super::{Error, Result, Storage};
34
35// ----------------------------------------------------------------------------
36// Traits
37// ----------------------------------------------------------------------------
38
39/// Attempt conversion into a [`Storage`] reference.
40///
41/// This trait implements conversion of an [`Any`] reference to an immutable
42/// [`Storage`] reference, which is used as the inputs of a scheduler action.
43pub trait TryAsStorage<K>: Value {
44 /// Attempts to convert into a storage reference.
45 ///
46 /// # Errors
47 ///
48 /// In case conversion fails, an error should be returned. Since this trait
49 /// is intended to be used in a low-level context, orchestrating conversion
50 /// of storages within actions, the errors just carry enough information so
51 /// the reason of the failure can be determined during development.
52 fn try_as_storage(item: &dyn Any) -> Result<&Storage<K, Self>>;
53}
54
55/// Attempt conversion into a mutable [`Storage`] reference.
56///
57/// This trait implements conversion of a mutable [`Any`] reference to a mutable
58/// [`Storage`] reference, which is used as the output of a scheduler action.
59pub trait TryAsStorageMut<K>: Value {
60 /// Attempts to convert into a mutable storage reference.
61 ///
62 /// # Errors
63 ///
64 /// In case conversion fails, an error should be returned. Since this trait
65 /// is intended to be used in a low-level context, orchestrating conversion
66 /// of storages within actions, the errors just carry enough information so
67 /// the reason of the failure can be determined during development.
68 fn try_as_storage_mut(item: &mut dyn Any) -> Result<&mut Storage<K, Self>>;
69}
70
71// ----------------------------------------------------------------------------
72
73/// Attempt conversion into a [`Storage`] sequence or tuple.
74///
75/// This trait implements conversion of an iterator of [`Any`] references to
76/// one or more storages, which can be a sequence or tuple.
77///
78/// __Warning__: Implementation requires the use of generic associated types,
79/// as lifetimes need to be passed through the conversion process.
80pub trait TryAsStorages<K>: Value {
81 /// Target type of conversion.
82 type Target<'a>: Debug;
83
84 /// Attempts to convert into a tuple of storage references.
85 ///
86 /// While 1-tuples are converted to a single storage reference, tuples with
87 /// multiple items are converted to a tuple of storage references, which is
88 /// more ergonomic to work with in the context of actions, since 1-tuples
89 /// would require awkward destructuring.
90 ///
91 /// # Errors
92 ///
93 /// The following errors might be returned:
94 ///
95 /// - [`Error::Mismatch`]: Number of items does not match.
96 /// - [`Error::Downcast`]: Item cannot be downcast.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// # use std::error::Error;
102 /// # fn main() -> Result<(), Box<dyn Error>> {
103 /// use std::any::Any;
104 /// use zrx_storage::Storage;
105 /// use zrx_storage::convert::TryAsStorages;
106 ///
107 /// // Create storages from iterators
108 /// let a = Storage::from_iter([("key", 42)]);
109 /// let b = Storage::from_iter([("key", true)]);
110 ///
111 /// // Obtain type-erased references
112 /// let iter: Vec<&dyn Any> = vec![&a, &b];
113 ///
114 /// // Obtain storage references
115 /// let storages = <(i32, bool)>::try_as_storages(iter)?;
116 /// # let _: (&Storage<&str, _>, _) = storages;
117 /// # Ok(())
118 /// # }
119 /// ```
120 fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
121 where
122 T: IntoIterator<Item = &'a dyn Any>;
123}
124
125// ----------------------------------------------------------------------------
126// Blanket implementations
127// ----------------------------------------------------------------------------
128
129impl<K, V> TryAsStorage<K> for V
130where
131 K: Key,
132 V: Value,
133{
134 /// Attempts to convert into a storage reference.
135 ///
136 /// # Errors
137 ///
138 /// The following errors might be returned:
139 ///
140 /// - [`Error::Downcast`]: Item cannot be downcast.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// # use std::error::Error;
146 /// # fn main() -> Result<(), Box<dyn Error>> {
147 /// use std::any::Any;
148 /// use zrx_storage::Storage;
149 /// use zrx_storage::convert::TryAsStorage;
150 ///
151 /// // Create storage and initial state
152 /// let mut storage = Storage::default();
153 /// storage.insert("key", 42);
154 ///
155 /// // Obtain type-erased reference
156 /// let item: &dyn Any = &storage;
157 ///
158 /// // Obtain storage reference
159 /// let storage = <i32>::try_as_storage(item)?;
160 /// # let _: &Storage<&str, _> = storage;
161 /// # Ok(())
162 /// # }
163 /// ```
164 #[inline]
165 fn try_as_storage(item: &dyn Any) -> Result<&Storage<K, Self>> {
166 item.downcast_ref().ok_or(Error::Downcast)
167 }
168}
169
170impl<K, V> TryAsStorageMut<K> for V
171where
172 K: Key,
173 V: Value,
174{
175 /// Attempts to convert into a mutable storage reference.
176 ///
177 /// # Errors
178 ///
179 /// The following errors might be returned:
180 ///
181 /// - [`Error::Downcast`]: Item cannot be downcast.
182 ///
183 /// # Examples
184 ///
185 /// ```
186 /// # use std::error::Error;
187 /// # fn main() -> Result<(), Box<dyn Error>> {
188 /// use std::any::Any;
189 /// use zrx_storage::Storage;
190 /// use zrx_storage::convert::TryAsStorageMut;
191 ///
192 /// // Create storage and initial state
193 /// let mut storage = Storage::default();
194 /// storage.insert("key", 42);
195 ///
196 /// // Obtain mutable type-erased reference
197 /// let item: &mut dyn Any = &mut storage;
198 ///
199 /// // Obtain mutable storage reference
200 /// let storage = <i32>::try_as_storage_mut(item)?;
201 /// # let _: &mut Storage<&str, _> = storage;
202 /// # Ok(())
203 /// # }
204 /// ```
205 #[inline]
206 fn try_as_storage_mut(item: &mut dyn Any) -> Result<&mut Storage<K, Self>> {
207 item.downcast_mut().ok_or(Error::Downcast)
208 }
209}
210
211// ----------------------------------------------------------------------------
212
213impl<K> TryAsStorages<K> for ()
214where
215 K: Key,
216{
217 type Target<'a> = ();
218
219 /// Attempts to convert into a unit value.
220 #[inline]
221 fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
222 where
223 T: IntoIterator<Item = &'a dyn Any>,
224 {
225 match iter.into_iter().next() {
226 Some(_) => Err(Error::Mismatch),
227 None => Ok(()),
228 }
229 }
230}
231
232impl<K, V> TryAsStorages<K> for Vec<V>
233where
234 K: Key,
235 V: TryAsStorage<K>,
236{
237 type Target<'a> = Vec<&'a Storage<K, V>>;
238
239 /// Attempts to convert into a sequence of storage references.
240 ///
241 /// # Errors
242 ///
243 /// The following errors might be returned:
244 ///
245 /// - [`Error::Downcast`]: Item cannot be downcast.
246 ///
247 /// # Examples
248 ///
249 /// ```
250 /// # use std::error::Error;
251 /// # fn main() -> Result<(), Box<dyn Error>> {
252 /// use std::any::Any;
253 /// use zrx_storage::Storage;
254 /// use zrx_storage::convert::TryAsStorages;
255 ///
256 /// // Create storages from iterators
257 /// let a = Storage::from_iter([("key", 42)]);
258 /// let b = Storage::from_iter([("key", 84)]);
259 ///
260 /// // Obtain type-erased references
261 /// let iter: Vec<&dyn Any> = vec![&a, &b];
262 ///
263 /// // Obtain storage references
264 /// let storages = <Vec<i32>>::try_as_storages(iter)?;
265 /// # let _: Vec<&Storage<&str, _>> = storages;
266 /// # Ok(())
267 /// # }
268 /// ```
269 #[inline]
270 fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
271 where
272 T: IntoIterator<Item = &'a dyn Any>,
273 {
274 iter.into_iter().map(V::try_as_storage).collect()
275 }
276}
277
278// ----------------------------------------------------------------------------
279// Macros
280// ----------------------------------------------------------------------------
281
282/// Implements storage conversion trait for a tuple.
283macro_rules! impl_try_as_storages_for_tuple {
284 ($($V:ident),+ $(,)?) => {
285 impl<K, $($V),+> TryAsStorages<K> for ($($V,)+)
286 where
287 K: Key,
288 $($V: TryAsStorage<K>,)+
289 {
290 #[allow(unused_parens)]
291 type Target<'a> = ($(&'a Storage<K, $V>),+);
292
293 #[inline]
294 fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
295 where
296 T: IntoIterator<Item = &'a dyn Any>,
297 {
298 let mut iter = iter.into_iter();
299 $(
300 #[allow(non_snake_case)]
301 let $V = $V::try_as_storage(
302 iter.next().ok_or(Error::Mismatch)?
303 )?;
304 )+
305
306 // Ensure that the iterator yields no more values
307 if iter.next().is_none() {
308 Ok(($($V),+))
309 } else {
310 Err(Error::Mismatch)
311 }
312 }
313 }
314 };
315}
316
317// ----------------------------------------------------------------------------
318
319impl_try_as_storages_for_tuple!(V1);
320impl_try_as_storages_for_tuple!(V1, V2);
321impl_try_as_storages_for_tuple!(V1, V2, V3);
322impl_try_as_storages_for_tuple!(V1, V2, V3, V4);
323impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5);
324impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5, V6);
325impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5, V6, V7);
326impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5, V6, V7, V8);