Skip to main content

bevy_reflect/
convert.rs

1//! The [`ReflectConvert`] type, which allows types to register conversions to
2//! and from one another.
3
4use alloc::boxed::Box;
5use core::{any::TypeId, marker::PhantomData};
6
7use bevy_utils::TypeIdMap;
8
9use crate::{Reflect, TypePath};
10
11/// Provides a mechanism for converting values of one type to another.
12///
13/// This [`crate::type_registry::TypeData`] is associated with the type to be
14/// converted *into*, not the type to be converted *from*. To convert a value,
15/// use code like the following:
16///
17/// ```rust
18/// # use bevy_reflect::{convert::ReflectConvert, GetTypeRegistration, TypeRegistry};
19/// # use std::any::TypeId;
20/// #
21/// # let mut registry = TypeRegistry::default();
22/// # registry.add_registration(i32::get_type_registration());
23/// # registry.add_registration(String::get_type_registration());
24/// # registry.register_type_conversion(|x: i32| Ok(x.to_string()));
25///
26/// let reflect_convert = registry
27///     .get_type_data::<ReflectConvert>(TypeId::of::<String>())
28///     .unwrap();
29/// let converted: String = *reflect_convert
30///     .try_convert_from(Box::new(12345i32))
31///     .unwrap()
32///     .downcast::<String>()
33///     .unwrap();
34/// ```
35#[derive(Default)]
36pub struct ReflectConvert {
37    /// A mapping from the type to be converted *from* to its associated
38    /// [`Converter`].
39    conversions: TypeIdMap<Box<dyn Converter>>,
40}
41
42/// An internal trait that wraps a conversion function in an untyped interface.
43trait Converter: Send + Sync {
44    /// Converts the value to the appropriate type.
45    ///
46    /// This returns the converted value if the conversion succeeds or the
47    /// original value if the conversion fails.
48    fn convert(&self, input: Box<dyn Reflect>) -> Result<Box<dyn Reflect>, Box<dyn Reflect>>;
49
50    /// Returns a new boxed instance wrapping the same [`Converter`].
51    fn clone_converter(&self) -> Box<dyn Converter>;
52}
53
54/// A wrapper that contains a conversion function and implements [`Converter`].
55struct TypedConverter<T, U, F>
56where
57    T: Reflect + TypePath,
58    U: Reflect + TypePath,
59    F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
60{
61    function: F,
62    phantom: PhantomData<(T, U)>,
63}
64
65impl ReflectConvert {
66    /// Attempts to construct an instance of this type from the provided
67    /// `input`.
68    ///
69    /// If the conversion fails, either because no conversion has been
70    /// registered from the type of `input` or because the conversion function
71    /// itself returned `Err`, the `input` value is returned as an error.
72    pub fn try_convert_from(
73        &self,
74        input: Box<dyn Reflect>,
75    ) -> Result<Box<dyn Reflect>, Box<dyn Reflect>> {
76        let type_id = (*input.as_any()).type_id();
77        match self.conversions.get(&type_id) {
78            Some(converter) => converter.convert(input),
79            None => Err(input),
80        }
81    }
82
83    /// Adds a conversion function from the type `T` to this type.
84    ///
85    /// If the conversion succeeds, the function should return the converted
86    /// value. If the conversion fails, the function should return the original
87    /// input value.
88    pub fn register_type_conversion<T, U, F>(&mut self, function: F)
89    where
90        T: Reflect + TypePath,
91        U: Reflect + TypePath,
92        F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
93    {
94        self.conversions.insert(
95            TypeId::of::<T>(),
96            Box::new(TypedConverter {
97                function,
98                phantom: PhantomData,
99            }),
100        );
101    }
102}
103
104impl Clone for ReflectConvert {
105    fn clone(&self) -> Self {
106        ReflectConvert {
107            conversions: self
108                .conversions
109                .iter()
110                .map(|(type_id, converter)| (*type_id, converter.clone_converter()))
111                .collect(),
112        }
113    }
114}
115
116impl<T, U, F> Clone for TypedConverter<T, U, F>
117where
118    T: Reflect + TypePath,
119    U: Reflect + TypePath,
120    F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
121{
122    fn clone(&self) -> Self {
123        TypedConverter {
124            function: self.function.clone(),
125            phantom: PhantomData,
126        }
127    }
128}
129
130impl<T, U, F> Converter for TypedConverter<T, U, F>
131where
132    T: Reflect + TypePath,
133    U: Reflect + TypePath,
134    F: Fn(T) -> Result<U, T> + Clone + Send + Sync + 'static,
135{
136    fn convert(&self, input: Box<dyn Reflect>) -> Result<Box<dyn Reflect>, Box<dyn Reflect>> {
137        let mut input = input.downcast::<T>()?;
138        match (self.function)(*input) {
139            Ok(value) => Ok(Box::new(value)),
140            Err(value) => {
141                *input = value;
142                Err(input)
143            }
144        }
145    }
146
147    fn clone_converter(&self) -> Box<dyn Converter> {
148        Box::new(self.clone())
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use alloc::{
155        borrow::ToOwned as _,
156        boxed::Box,
157        string::{String, ToString},
158    };
159    use core::any::TypeId;
160
161    use crate::{convert::ReflectConvert, type_registry::GetTypeRegistration, TypeRegistry};
162
163    /// Tests that `i32` can be converted to `String` if the appropriate
164    /// conversion is registered.
165    #[test]
166    fn convert_from_i32_to_string() {
167        // Register the types and the conversion.
168        let mut registry = TypeRegistry::default();
169        registry.add_registration(i32::get_type_registration());
170        registry.add_registration(String::get_type_registration());
171        registry.register_type_conversion(|x: i32| Ok(x.to_string()));
172
173        let reflect_convert = registry
174            .get_type_data::<ReflectConvert>(TypeId::of::<String>())
175            .unwrap();
176
177        // Test that a successful conversion works.
178        let converted = reflect_convert
179            .try_convert_from(Box::new(12345i32))
180            .unwrap()
181            .downcast::<String>()
182            .unwrap();
183        assert_eq!(&**converted, "12345");
184    }
185
186    /// Tests that `String` can be fallibly converted to `i32` if the
187    /// appropriate conversion is registered.
188    ///
189    /// This also tests that the behavior of returning the original string on
190    /// error is correct.
191    #[test]
192    fn convert_from_string_to_i32() {
193        // Register the types and the conversion.
194        let mut registry = TypeRegistry::default();
195        registry.add_registration(i32::get_type_registration());
196        registry.add_registration(String::get_type_registration());
197        registry.register_type_conversion(|x: String| match x.parse::<i32>() {
198            Ok(value) => Ok(value),
199            Err(_) => Err(x),
200        });
201
202        let reflect_convert = registry
203            .get_type_data::<ReflectConvert>(TypeId::of::<i32>())
204            .unwrap();
205
206        // Test a successful conversion from string to integer.
207        let converted = reflect_convert
208            .try_convert_from(Box::new("12345".to_owned()))
209            .unwrap()
210            .downcast::<i32>()
211            .unwrap();
212        assert_eq!(*converted, 12345);
213
214        // Test an unsuccessful conversion from string to integer.
215        let error = reflect_convert
216            .try_convert_from(Box::new("qqqqq".to_owned()))
217            .unwrap_err()
218            .downcast::<String>()
219            .unwrap();
220        assert_eq!(&**error, "qqqqq");
221    }
222
223    /// Tests that we can register multiple conversions into the same type and
224    /// that they all work.
225    #[test]
226    fn convert_from_f32_and_u32_to_i32() {
227        let mut registry = TypeRegistry::default();
228        registry.add_registration(i32::get_type_registration());
229        registry.add_registration(f32::get_type_registration());
230        registry.add_registration(u32::get_type_registration());
231        registry.register_type_conversion::<u32, i32, _>(|n: u32| n.try_into().map_err(|_| n));
232        registry.register_type_conversion::<f32, i32, _>(|n: f32| Ok(n as i32));
233
234        let reflect_convert = registry
235            .get_type_data::<ReflectConvert>(TypeId::of::<i32>())
236            .unwrap();
237
238        // Test that we can convert `u32` and `f32` into `i32`.
239        let a = reflect_convert
240            .try_convert_from(Box::new(99u32))
241            .unwrap()
242            .downcast::<i32>()
243            .unwrap();
244        assert_eq!(*a, 99i32);
245        let b = reflect_convert
246            .try_convert_from(Box::new(99.0f32))
247            .unwrap()
248            .downcast::<i32>()
249            .unwrap();
250        assert_eq!(*b, 99i32);
251    }
252
253    /// Tests that the error-handling behavior is correct when attempting a
254    /// conversion that hasn't been registered.
255    #[test]
256    fn no_such_conversion() {
257        let mut registry = TypeRegistry::default();
258        registry.add_registration(i32::get_type_registration());
259        registry.add_registration(String::get_type_registration());
260        registry
261            .get_mut(TypeId::of::<i32>())
262            .unwrap()
263            .insert(ReflectConvert::default());
264
265        let reflect_convert = registry
266            .get_type_data::<ReflectConvert>(TypeId::of::<i32>())
267            .unwrap();
268
269        // Test that we get the original value back on error.
270        let error = reflect_convert
271            .try_convert_from(Box::new("12345".to_owned()))
272            .unwrap_err()
273            .downcast::<String>()
274            .unwrap();
275        assert_eq!(&**error, "12345");
276    }
277}