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.
43///
44/// __Warning__: Implementation requires the use of generic associated types,
45/// as lifetimes need to be passed through the conversion process.
46pub trait TryAsStorage<K>: Value {
47    /// Target type of conversion.
48    type Target<'a>: Debug;
49
50    /// Attempts to convert into a storage reference.
51    ///
52    /// # Errors
53    ///
54    /// In case conversion fails, an error should be returned. Since this trait
55    /// is intended to be used in a low-level context, orchestrating conversion
56    /// of storages within actions, the errors just carry enough information so
57    /// the reason of the failure can be determined during development.
58    fn try_as_storage(item: &dyn Any) -> Result<Self::Target<'_>>;
59}
60
61/// Attempt conversion into a mutable [`Storage`] reference.
62///
63/// This trait implements conversion of a mutable [`Any`] reference to a mutable
64/// [`Storage`] reference, which is used as the output of a scheduler action.
65///
66/// __Warning__: Implementation requires the use of generic associated types,
67/// as lifetimes need to be passed through the conversion process.
68pub trait TryAsStorageMut<K>: Value {
69    /// Target type of conversion.
70    type Target<'a>: Debug;
71
72    /// Attempts to convert into a mutable storage reference.
73    ///
74    /// # Errors
75    ///
76    /// In case conversion fails, an error should be returned. Since this trait
77    /// is intended to be used in a low-level context, orchestrating conversion
78    /// of storages within actions, the errors just carry enough information so
79    /// the reason of the failure can be determined during development.
80    fn try_as_storage_mut(item: &mut dyn Any) -> Result<Self::Target<'_>>;
81}
82
83// ----------------------------------------------------------------------------
84
85/// Attempt conversion into a [`Storage`] sequence or tuple.
86///
87/// This trait implements conversion of an iterator of [`Any`] references to
88/// one or more storages, which can be a sequence or tuple.
89///
90/// __Warning__: Implementation requires the use of generic associated types,
91/// as lifetimes need to be passed through the conversion process.
92pub trait TryAsStorages<K> {
93    /// Target type of conversion.
94    type Target<'a>: Debug;
95
96    /// Attempts to convert into a tuple of storage references.
97    ///
98    /// While 1-tuples are converted to a single storage reference, tuples with
99    /// multiple items are converted to a tuple of storage references, which is
100    /// more ergonomic to work with in the context of actions, since 1-tuples
101    /// would require awkward destructuring.
102    ///
103    /// # Errors
104    ///
105    /// The following errors might be returned:
106    ///
107    /// - [`Error::Mismatch`]: Number of items does not match.
108    /// - [`Error::Downcast`]: Item cannot be downcast.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// # use std::error::Error;
114    /// # fn main() -> Result<(), Box<dyn Error>> {
115    /// use std::any::Any;
116    /// use zrx_storage::convert::TryAsStorages;
117    /// use zrx_storage::Storage;
118    ///
119    /// // Create storages from iterators
120    /// let a = Storage::from_iter([("key", 42)]);
121    /// let b = Storage::from_iter([("key", true)]);
122    ///
123    /// // Obtain type-erased references
124    /// let iter: Vec<&dyn Any> = vec![&a, &b];
125    ///
126    /// // Obtain storage references
127    /// let storages = <(i32, bool)>::try_as_storages(iter)?;
128    /// # let _: (&Storage<&str, _>, _) = storages;
129    /// # Ok(())
130    /// # }
131    /// ```
132    fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
133    where
134        T: IntoIterator<Item = &'a dyn Any>;
135}
136
137// ----------------------------------------------------------------------------
138// Blanket implementations
139// ----------------------------------------------------------------------------
140
141impl<K, V> TryAsStorage<K> for V
142where
143    K: Key,
144    V: Value,
145{
146    type Target<'a> = &'a Storage<K, V>;
147
148    /// Attempts to convert into a storage reference.
149    ///
150    /// # Errors
151    ///
152    /// The following errors might be returned:
153    ///
154    /// - [`Error::Downcast`]: Item cannot be downcast.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// # use std::error::Error;
160    /// # fn main() -> Result<(), Box<dyn Error>> {
161    /// use std::any::Any;
162    /// use zrx_storage::convert::TryAsStorage;
163    /// use zrx_storage::Storage;
164    ///
165    /// // Create storage and initial state
166    /// let mut storage = Storage::default();
167    /// storage.insert("key", 42);
168    ///
169    /// // Obtain type-erased reference
170    /// let item: &dyn Any = &storage;
171    ///
172    /// // Obtain storage reference
173    /// let storage = <i32>::try_as_storage(item)?;
174    /// # let _: &Storage<&str, _> = storage;
175    /// # Ok(())
176    /// # }
177    /// ```
178    #[inline]
179    fn try_as_storage(item: &dyn Any) -> Result<Self::Target<'_>> {
180        item.downcast_ref().ok_or(Error::Downcast)
181    }
182}
183
184impl<K, V> TryAsStorageMut<K> for V
185where
186    K: Key,
187    V: Value,
188{
189    type Target<'a> = &'a mut Storage<K, V>;
190
191    /// Attempts to convert into a mutable storage reference.
192    ///
193    /// # Errors
194    ///
195    /// The following errors might be returned:
196    ///
197    /// - [`Error::Downcast`]: Item cannot be downcast.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// # use std::error::Error;
203    /// # fn main() -> Result<(), Box<dyn Error>> {
204    /// use std::any::Any;
205    /// use zrx_storage::convert::TryAsStorageMut;
206    /// use zrx_storage::Storage;
207    ///
208    /// // Create storage and initial state
209    /// let mut storage = Storage::default();
210    /// storage.insert("key", 42);
211    ///
212    /// // Obtain mutable type-erased reference
213    /// let item: &mut dyn Any = &mut storage;
214    ///
215    /// // Obtain mutable storage reference
216    /// let storage = <i32>::try_as_storage_mut(item)?;
217    /// # let _: &mut Storage<&str, _> = storage;
218    /// # Ok(())
219    /// # }
220    /// ```
221    #[inline]
222    fn try_as_storage_mut(item: &mut dyn Any) -> Result<Self::Target<'_>> {
223        item.downcast_mut().ok_or(Error::Downcast)
224    }
225}
226
227// ----------------------------------------------------------------------------
228
229impl<K> TryAsStorages<K> for ()
230where
231    K: Key,
232{
233    type Target<'a> = ();
234
235    /// Attempts to convert into a unit value.
236    #[inline]
237    fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
238    where
239        T: IntoIterator<Item = &'a dyn Any>,
240    {
241        match iter.into_iter().next() {
242            Some(_) => Err(Error::Mismatch),
243            None => Ok(()),
244        }
245    }
246}
247
248impl<K, V> TryAsStorages<K> for [V]
249where
250    K: Key,
251    V: TryAsStorage<K>,
252{
253    type Target<'a> = Vec<V::Target<'a>>;
254
255    /// Attempts to convert into a sequence of storage references.
256    ///
257    /// # Errors
258    ///
259    /// The following errors might be returned:
260    ///
261    /// - [`Error::Downcast`]: Item cannot be downcast.
262    ///
263    /// # Examples
264    ///
265    /// ```
266    /// # use std::error::Error;
267    /// # fn main() -> Result<(), Box<dyn Error>> {
268    /// use std::any::Any;
269    /// use zrx_storage::convert::TryAsStorages;
270    /// use zrx_storage::Storage;
271    ///
272    /// // Create storages from iterators
273    /// let a = Storage::from_iter([("key", 42)]);
274    /// let b = Storage::from_iter([("key", 84)]);
275    ///
276    /// // Obtain type-erased references
277    /// let iter: Vec<&dyn Any> = vec![&a, &b];
278    ///
279    /// // Obtain storage references
280    /// let storages = <[i32]>::try_as_storages(iter)?;
281    /// # let _: Vec<&Storage<&str, _>> = storages;
282    /// # Ok(())
283    /// # }
284    /// ```
285    #[inline]
286    fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
287    where
288        T: IntoIterator<Item = &'a dyn Any>,
289    {
290        iter.into_iter().map(V::try_as_storage).collect()
291    }
292}
293
294// ----------------------------------------------------------------------------
295// Macros
296// ----------------------------------------------------------------------------
297
298/// Implements storage conversion trait for a tuple.
299macro_rules! impl_try_as_storages_for_tuple {
300    ($($V:ident),+ $(,)?) => {
301        impl<K, $($V),+> TryAsStorages<K> for ($($V,)+)
302        where
303            K: Key,
304            $($V: TryAsStorage<K>,)+
305        {
306            #[allow(unused_parens)]
307            type Target<'a> = ($($V::Target<'a>),+);
308
309            #[inline]
310            fn try_as_storages<'a, T>(iter: T) -> Result<Self::Target<'a>>
311            where
312                T: IntoIterator<Item = &'a dyn Any>,
313            {
314                let mut iter = iter.into_iter();
315                $(
316                    #[allow(non_snake_case)]
317                    let $V = $V::try_as_storage(
318                        iter.next().ok_or(Error::Mismatch)?
319                    )?;
320                )+
321
322                // Ensure that the iterator yields no more values
323                if iter.next().is_none() {
324                    Ok(($($V),+))
325                } else {
326                    Err(Error::Mismatch)
327                }
328            }
329        }
330    };
331}
332
333// ----------------------------------------------------------------------------
334
335impl_try_as_storages_for_tuple!(V1);
336impl_try_as_storages_for_tuple!(V1, V2);
337impl_try_as_storages_for_tuple!(V1, V2, V3);
338impl_try_as_storages_for_tuple!(V1, V2, V3, V4);
339impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5);
340impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5, V6);
341impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5, V6, V7);
342impl_try_as_storages_for_tuple!(V1, V2, V3, V4, V5, V6, V7, V8);