aws_smithy_types/
type_erasure.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::any::Any;
7use std::error::Error as StdError;
8use std::fmt;
9use std::sync::Arc;
10
11/// Abstraction over `Box<dyn T + Send + Sync>` that provides `Debug` and optionally `Clone`.
12///
13/// The orchestrator uses `TypeErasedBox` to avoid the complication of six or more generic parameters
14/// and to avoid the monomorphization that brings with it.
15///
16/// # Examples
17///
18/// Creating a box:
19/// ```no_run
20/// use aws_smithy_types::type_erasure::TypeErasedBox;
21///
22/// let boxed = TypeErasedBox::new("some value");
23/// ```
24///
25/// Downcasting a box:
26/// ```no_run
27/// # use aws_smithy_types::type_erasure::TypeErasedBox;
28/// # let boxed = TypeErasedBox::new("some value".to_string());
29/// let value: Option<&String> = boxed.downcast_ref::<String>();
30/// ```
31///
32/// Converting a box back into its value:
33/// ```
34/// # use aws_smithy_types::type_erasure::TypeErasedBox;
35/// let boxed = TypeErasedBox::new("some value".to_string());
36/// let value: String = *boxed.downcast::<String>().expect("it is a string");
37/// ```
38pub struct TypeErasedBox {
39    field: Box<dyn Any + Send + Sync>,
40    #[allow(clippy::type_complexity)]
41    debug: Arc<
42        dyn Fn(&Box<dyn Any + Send + Sync>, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
43    >,
44    #[allow(clippy::type_complexity)]
45    clone: Option<Arc<dyn Fn(&Box<dyn Any + Send + Sync>) -> TypeErasedBox + Send + Sync>>,
46}
47
48#[cfg(feature = "test-util")]
49impl TypeErasedBox {
50    /// Often, when testing the orchestrator or its components, it's necessary to provide a
51    /// `TypeErasedBox` to serve as an `Input` for `invoke`. In cases where the type won't actually
52    /// be accessed during testing, use this method to generate a `TypeErasedBox` that makes it
53    /// clear that "for the purpose of this test, the `Input` doesn't matter."
54    pub fn doesnt_matter() -> Self {
55        Self::new("doesn't matter")
56    }
57}
58
59impl fmt::Debug for TypeErasedBox {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str("TypeErasedBox[")?;
62        if self.clone.is_some() {
63            f.write_str("Clone")?;
64        } else {
65            f.write_str("!Clone")?;
66        }
67        f.write_str("]:")?;
68        (self.debug)(&self.field, f)
69    }
70}
71
72impl TypeErasedBox {
73    /// Create a new `TypeErasedBox` from `value` of type `T`
74    pub fn new<T: Send + Sync + fmt::Debug + 'static>(value: T) -> Self {
75        let debug = |value: &Box<dyn Any + Send + Sync>, f: &mut fmt::Formatter<'_>| {
76            fmt::Debug::fmt(value.downcast_ref::<T>().expect("type-checked"), f)
77        };
78        Self {
79            field: Box::new(value),
80            debug: Arc::new(debug),
81            clone: None,
82        }
83    }
84
85    /// Create a new cloneable `TypeErasedBox` from the given `value`.
86    pub fn new_with_clone<T: Send + Sync + Clone + fmt::Debug + 'static>(value: T) -> Self {
87        let debug = |value: &Box<dyn Any + Send + Sync>, f: &mut fmt::Formatter<'_>| {
88            fmt::Debug::fmt(value.downcast_ref::<T>().expect("type-checked"), f)
89        };
90        let clone = |value: &Box<dyn Any + Send + Sync>| {
91            TypeErasedBox::new_with_clone(value.downcast_ref::<T>().expect("typechecked").clone())
92        };
93        Self {
94            field: Box::new(value),
95            debug: Arc::new(debug),
96            clone: Some(Arc::new(clone)),
97        }
98    }
99
100    /// Attempts to clone this box.
101    ///
102    /// Note: this will only ever succeed if the box was created with [`TypeErasedBox::new_with_clone`].
103    pub fn try_clone(&self) -> Option<Self> {
104        Some((self.clone.as_ref()?)(&self.field))
105    }
106
107    /// Downcast into a `Box<T>`, or return `Self` if it is not a `T`.
108    pub fn downcast<T: fmt::Debug + Send + Sync + 'static>(self) -> Result<Box<T>, Self> {
109        let TypeErasedBox {
110            field,
111            debug,
112            clone,
113        } = self;
114        field.downcast().map_err(|field| Self {
115            field,
116            debug,
117            clone,
118        })
119    }
120
121    /// Downcast as a `&T`, or return `None` if it is not a `T`.
122    pub fn downcast_ref<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
123        self.field.downcast_ref()
124    }
125
126    /// Downcast as a `&mut T`, or return `None` if it is not a `T`.
127    pub fn downcast_mut<T: fmt::Debug + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
128        self.field.downcast_mut()
129    }
130}
131
132impl From<TypeErasedError> for TypeErasedBox {
133    fn from(value: TypeErasedError) -> Self {
134        TypeErasedBox {
135            field: value.field,
136            debug: value.debug,
137            clone: None,
138        }
139    }
140}
141
142/// A new-type around `Box<dyn Error + Debug + Send + Sync>` that also implements `Error`
143pub struct TypeErasedError {
144    field: Box<dyn Any + Send + Sync>,
145    #[allow(clippy::type_complexity)]
146    debug: Arc<
147        dyn Fn(&Box<dyn Any + Send + Sync>, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
148    >,
149    #[allow(clippy::type_complexity)]
150    as_error: Box<dyn for<'a> Fn(&'a TypeErasedError) -> &'a (dyn StdError) + Send + Sync>,
151}
152
153impl fmt::Debug for TypeErasedError {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.write_str("TypeErasedError:")?;
156        (self.debug)(&self.field, f)
157    }
158}
159
160impl fmt::Display for TypeErasedError {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        fmt::Display::fmt((self.as_error)(self), f)
163    }
164}
165
166impl StdError for TypeErasedError {
167    fn source(&self) -> Option<&(dyn StdError + 'static)> {
168        (self.as_error)(self).source()
169    }
170}
171
172impl TypeErasedError {
173    /// Create a new `TypeErasedError` from `value` of type `T`
174    pub fn new<T: StdError + Send + Sync + fmt::Debug + 'static>(value: T) -> Self {
175        let debug = |value: &Box<dyn Any + Send + Sync>, f: &mut fmt::Formatter<'_>| {
176            fmt::Debug::fmt(value.downcast_ref::<T>().expect("typechecked"), f)
177        };
178        Self {
179            field: Box::new(value),
180            debug: Arc::new(debug),
181            as_error: Box::new(|value: &TypeErasedError| {
182                value.downcast_ref::<T>().expect("typechecked") as _
183            }),
184        }
185    }
186
187    /// Downcast into a `Box<T>`, or return `Self` if it is not a `T`.
188    pub fn downcast<T: StdError + fmt::Debug + Send + Sync + 'static>(
189        self,
190    ) -> Result<Box<T>, Self> {
191        let TypeErasedError {
192            field,
193            debug,
194            as_error,
195        } = self;
196        field.downcast().map_err(|field| Self {
197            field,
198            debug,
199            as_error,
200        })
201    }
202
203    /// Downcast as a `&T`, or return `None` if it is not a `T`.
204    pub fn downcast_ref<T: StdError + fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
205        self.field.downcast_ref()
206    }
207
208    /// Downcast as a `&mut T`, or return `None` if it is not a `T`.
209    pub fn downcast_mut<T: StdError + fmt::Debug + Send + Sync + 'static>(
210        &mut self,
211    ) -> Option<&mut T> {
212        self.field.downcast_mut()
213    }
214
215    /// Returns a `TypeErasedError` with a fake/test value with the expectation that it won't be downcast in the test.
216    #[cfg(feature = "test-util")]
217    pub fn doesnt_matter() -> Self {
218        #[derive(Debug)]
219        struct FakeError;
220        impl fmt::Display for FakeError {
221            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222                write!(f, "FakeError")
223            }
224        }
225        impl StdError for FakeError {}
226        Self::new(FakeError)
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::{TypeErasedBox, TypeErasedError};
233    use std::fmt;
234
235    #[derive(Debug, Clone, PartialEq, Eq)]
236    struct TestErr {
237        inner: &'static str,
238    }
239
240    impl TestErr {
241        fn new(inner: &'static str) -> Self {
242            Self { inner }
243        }
244    }
245
246    impl fmt::Display for TestErr {
247        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248            write!(f, "Error: {}", self.inner)
249        }
250    }
251
252    impl std::error::Error for TestErr {}
253
254    #[test]
255    fn test_typed_erased_errors_can_be_downcast() {
256        let test_err = TestErr::new("something failed!");
257        let type_erased_test_err = TypeErasedError::new(test_err.clone());
258        let actual = type_erased_test_err
259            .downcast::<TestErr>()
260            .expect("type erased error can be downcast into original type");
261        assert_eq!(test_err, *actual);
262    }
263
264    #[test]
265    fn test_typed_erased_errors_can_be_unwrapped() {
266        let test_err = TestErr::new("something failed!");
267        let type_erased_test_err = TypeErasedError::new(test_err.clone());
268        let actual = *type_erased_test_err
269            .downcast::<TestErr>()
270            .expect("type erased error can be downcast into original type");
271        assert_eq!(test_err, actual);
272    }
273
274    #[test]
275    fn test_typed_cloneable_boxes() {
276        let expected_str = "I can be cloned";
277        let cloneable = TypeErasedBox::new_with_clone(expected_str.to_owned());
278        // ensure it can be cloned
279        let cloned = cloneable.try_clone().unwrap();
280        let actual_str = cloned.downcast_ref::<String>().unwrap();
281        assert_eq!(expected_str, actual_str);
282        // they should point to different addresses
283        assert_ne!(format!("{expected_str:p}"), format! {"{actual_str:p}"});
284    }
285}