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