Skip to main content

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);