icu_provider/
any.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Traits for data providers that produce `Any` objects.
6
7use crate::prelude::*;
8use crate::response::DataPayloadInner;
9use core::any::Any;
10use core::convert::TryFrom;
11use core::convert::TryInto;
12use yoke::trait_hack::YokeTraitHack;
13use yoke::Yokeable;
14use zerofrom::ZeroFrom;
15
16#[cfg(not(feature = "sync"))]
17use alloc::rc::Rc as SelectedRc;
18#[cfg(feature = "sync")]
19use alloc::sync::Arc as SelectedRc;
20
21/// A trait that allows to specify `Send + Sync` bounds that are only required when
22/// the `sync` Cargo feature is enabled. Without the Cargo feature, this is an empty bound.
23#[cfg(feature = "sync")]
24pub trait MaybeSendSync: Send + Sync {}
25#[cfg(feature = "sync")]
26impl<T: Send + Sync> MaybeSendSync for T {}
27
28#[allow(missing_docs)] // docs generated with all features
29#[cfg(not(feature = "sync"))]
30pub trait MaybeSendSync {}
31#[cfg(not(feature = "sync"))]
32impl<T> MaybeSendSync for T {}
33
34/// Representations of the `Any` trait object.
35///
36/// **Important Note:** The types enclosed by `StructRef` and `PayloadRc` are NOT the same!
37/// The first refers to the struct itself, whereas the second refers to a `DataPayload`.
38#[derive(Debug, Clone)]
39enum AnyPayloadInner {
40    /// A reference to `M::Yokeable`
41    StructRef(&'static dyn Any),
42    /// A boxed `DataPayload<M>`.
43    ///
44    /// Note: This needs to be reference counted, not a `Box`, so that `AnyPayload` is cloneable.
45    /// If an `AnyPayload` is cloned, the actual cloning of the data is delayed until
46    /// `downcast()` is invoked (at which point we have the concrete type).
47
48    #[cfg(not(feature = "sync"))]
49    PayloadRc(SelectedRc<dyn Any>),
50
51    #[cfg(feature = "sync")]
52    PayloadRc(SelectedRc<dyn Any + Send + Sync>),
53}
54
55/// A type-erased data payload.
56///
57/// The only useful method on this type is [`AnyPayload::downcast()`], which transforms this into
58/// a normal `DataPayload` which you can subsequently access or mutate.
59///
60/// As with `DataPayload`, cloning is designed to be cheap.
61#[derive(Debug, Clone, Yokeable)]
62pub struct AnyPayload {
63    inner: AnyPayloadInner,
64    type_name: &'static str,
65}
66
67/// The [`DataMarker`] marker type for [`AnyPayload`].
68#[allow(clippy::exhaustive_structs)] // marker type
69#[derive(Debug)]
70pub struct AnyMarker;
71
72impl DataMarker for AnyMarker {
73    type Yokeable = AnyPayload;
74}
75
76impl<M> crate::dynutil::UpcastDataPayload<M> for AnyMarker
77where
78    M: DataMarker,
79    M::Yokeable: MaybeSendSync,
80{
81    #[inline]
82    fn upcast(other: DataPayload<M>) -> DataPayload<AnyMarker> {
83        DataPayload::from_owned(other.wrap_into_any_payload())
84    }
85}
86
87impl AnyPayload {
88    /// Transforms a type-erased `AnyPayload` into a concrete `DataPayload<M>`.
89    ///
90    /// Because it is expected that the call site knows the identity of the AnyPayload (e.g., from
91    /// the data request), this function returns a `DataError` if the generic type does not match
92    /// the type stored in the `AnyPayload`.
93    pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
94    where
95        M: DataMarker,
96        // For the StructRef case:
97        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
98        // For the PayloadRc case:
99        M::Yokeable: MaybeSendSync,
100        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
101    {
102        use AnyPayloadInner::*;
103        let type_name = self.type_name;
104        match self.inner {
105            StructRef(any_ref) => {
106                let down_ref: &'static M::Yokeable = any_ref
107                    .downcast_ref()
108                    .ok_or_else(|| DataError::for_type::<M>().with_str_context(type_name))?;
109                Ok(DataPayload::from_static_ref(down_ref))
110            }
111            PayloadRc(any_rc) => {
112                let down_rc = any_rc
113                    .downcast::<DataPayload<M>>()
114                    .map_err(|_| DataError::for_type::<M>().with_str_context(type_name))?;
115                Ok(SelectedRc::try_unwrap(down_rc).unwrap_or_else(|down_rc| (*down_rc).clone()))
116            }
117        }
118    }
119
120    /// Clones and then transforms a type-erased `AnyPayload` into a concrete `DataPayload<M>`.
121    pub fn downcast_cloned<M>(&self) -> Result<DataPayload<M>, DataError>
122    where
123        M: DataMarker,
124        // For the StructRef case:
125        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
126        // For the PayloadRc case:
127        M::Yokeable: MaybeSendSync,
128        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
129    {
130        self.clone().downcast()
131    }
132
133    /// Creates an `AnyPayload` from a static reference to a data struct.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use icu_provider::hello_world::*;
139    /// use icu_provider::prelude::*;
140    /// use std::borrow::Cow;
141    ///
142    /// const HELLO_DATA: HelloWorldV1<'static> = HelloWorldV1 {
143    ///     message: Cow::Borrowed("Custom Hello World"),
144    /// };
145    ///
146    /// let any_payload = AnyPayload::from_static_ref(&HELLO_DATA);
147    ///
148    /// let payload: DataPayload<HelloWorldV1Marker> =
149    ///     any_payload.downcast().expect("TypeId matches");
150    /// assert_eq!("Custom Hello World", payload.get().message);
151    /// ```
152    pub fn from_static_ref<Y>(static_ref: &'static Y) -> Self
153    where
154        Y: for<'a> Yokeable<'a>,
155    {
156        AnyPayload {
157            inner: AnyPayloadInner::StructRef(static_ref),
158            // Note: This records the Yokeable type rather than the DataMarker type,
159            // but that is okay since this is only for debugging
160            type_name: core::any::type_name::<Y>(),
161        }
162    }
163}
164
165impl<M> DataPayload<M>
166where
167    M: DataMarker,
168    M::Yokeable: MaybeSendSync,
169{
170    /// Converts this DataPayload into a type-erased `AnyPayload`. Unless the payload stores a static
171    /// reference, this will move it to the heap.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use icu_provider::hello_world::*;
177    /// use icu_provider::prelude::*;
178    /// use std::borrow::Cow;
179    ///
180    /// let payload: DataPayload<HelloWorldV1Marker> =
181    ///     DataPayload::from_owned(HelloWorldV1 {
182    ///         message: Cow::Borrowed("Custom Hello World"),
183    ///     });
184    ///
185    /// let any_payload = payload.wrap_into_any_payload();
186    ///
187    /// let payload: DataPayload<HelloWorldV1Marker> =
188    ///     any_payload.downcast().expect("TypeId matches");
189    /// assert_eq!("Custom Hello World", payload.get().message);
190    /// ```
191    pub fn wrap_into_any_payload(self) -> AnyPayload {
192        AnyPayload {
193            inner: match self.0 {
194                DataPayloadInner::StaticRef(r) => AnyPayloadInner::StructRef(r),
195                inner => AnyPayloadInner::PayloadRc(SelectedRc::from(Self(inner))),
196            },
197            type_name: core::any::type_name::<M>(),
198        }
199    }
200}
201
202impl DataPayload<AnyMarker> {
203    /// Transforms a type-erased `DataPayload<AnyMarker>` into a concrete `DataPayload<M>`.
204    #[inline]
205    pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
206    where
207        M: DataMarker,
208        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
209        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
210        M::Yokeable: MaybeSendSync,
211    {
212        self.try_unwrap_owned()?.downcast()
213    }
214}
215
216/// A [`DataResponse`] for type-erased values.
217///
218/// Convertible to and from `DataResponse<AnyMarker>`.
219#[allow(clippy::exhaustive_structs)] // this type is stable (the metadata is allowed to grow)
220#[derive(Debug)]
221pub struct AnyResponse {
222    /// Metadata about the returned object.
223    pub metadata: DataResponseMetadata,
224
225    /// The object itself; `None` if it was not loaded.
226    pub payload: Option<AnyPayload>,
227}
228
229impl TryFrom<DataResponse<AnyMarker>> for AnyResponse {
230    type Error = DataError;
231    #[inline]
232    fn try_from(other: DataResponse<AnyMarker>) -> Result<Self, Self::Error> {
233        Ok(Self {
234            metadata: other.metadata,
235            payload: other.payload.map(|p| p.try_unwrap_owned()).transpose()?,
236        })
237    }
238}
239
240impl From<AnyResponse> for DataResponse<AnyMarker> {
241    #[inline]
242    fn from(other: AnyResponse) -> Self {
243        Self {
244            metadata: other.metadata,
245            payload: other.payload.map(DataPayload::from_owned),
246        }
247    }
248}
249
250impl AnyResponse {
251    /// Transforms a type-erased `AnyResponse` into a concrete `DataResponse<M>`.
252    #[inline]
253    pub fn downcast<M>(self) -> Result<DataResponse<M>, DataError>
254    where
255        M: DataMarker,
256        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
257        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
258        M::Yokeable: MaybeSendSync,
259    {
260        Ok(DataResponse {
261            metadata: self.metadata,
262            payload: self.payload.map(|p| p.downcast()).transpose()?,
263        })
264    }
265
266    /// Clones and then transforms a type-erased `AnyResponse` into a concrete `DataResponse<M>`.
267    pub fn downcast_cloned<M>(&self) -> Result<DataResponse<M>, DataError>
268    where
269        M: DataMarker,
270        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
271        M::Yokeable: MaybeSendSync,
272        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
273    {
274        Ok(DataResponse {
275            metadata: self.metadata.clone(),
276            payload: self
277                .payload
278                .as_ref()
279                .map(|p| p.downcast_cloned())
280                .transpose()?,
281        })
282    }
283}
284
285impl<M> DataResponse<M>
286where
287    M: DataMarker,
288    M::Yokeable: MaybeSendSync,
289{
290    /// Moves the inner DataPayload to the heap (requiring an allocation) and returns it as an
291    /// erased `AnyResponse`.
292    pub fn wrap_into_any_response(self) -> AnyResponse {
293        AnyResponse {
294            metadata: self.metadata,
295            payload: self.payload.map(|p| p.wrap_into_any_payload()),
296        }
297    }
298}
299
300/// An object-safe data provider that returns data structs cast to `dyn Any` trait objects.
301///
302/// # Examples
303///
304/// ```
305/// use icu_provider::hello_world::*;
306/// use icu_provider::prelude::*;
307/// use std::borrow::Cow;
308///
309/// let any_provider = HelloWorldProvider.as_any_provider();
310///
311/// let req = DataRequest {
312///     locale: &icu_locid::langid!("de").into(),
313///     metadata: Default::default(),
314/// };
315///
316/// // Downcasting manually
317/// assert_eq!(
318///     any_provider
319///         .load_any(HelloWorldV1Marker::KEY, req)
320///         .expect("load should succeed")
321///         .downcast::<HelloWorldV1Marker>()
322///         .expect("types should match")
323///         .take_payload()
324///         .unwrap()
325///         .get(),
326///     &HelloWorldV1 {
327///         message: Cow::Borrowed("Hallo Welt"),
328///     },
329/// );
330///
331/// // Downcasting automatically
332/// let downcasting_provider: &dyn DataProvider<HelloWorldV1Marker> =
333///     &any_provider.as_downcasting();
334///
335/// assert_eq!(
336///     downcasting_provider
337///         .load(req)
338///         .expect("load should succeed")
339///         .take_payload()
340///         .unwrap()
341///         .get(),
342///     &HelloWorldV1 {
343///         message: Cow::Borrowed("Hallo Welt"),
344///     },
345/// );
346/// ```
347pub trait AnyProvider {
348    /// Loads an [`AnyPayload`] according to the key and request.
349    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError>;
350}
351
352impl<'a, T: AnyProvider + ?Sized> AnyProvider for &'a T {
353    #[inline]
354    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
355        (**self).load_any(key, req)
356    }
357}
358
359impl<T: AnyProvider + ?Sized> AnyProvider for alloc::boxed::Box<T> {
360    #[inline]
361    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
362        (**self).load_any(key, req)
363    }
364}
365
366impl<T: AnyProvider + ?Sized> AnyProvider for alloc::rc::Rc<T> {
367    #[inline]
368    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
369        (**self).load_any(key, req)
370    }
371}
372
373#[cfg(target_has_atomic = "ptr")]
374impl<T: AnyProvider + ?Sized> AnyProvider for alloc::sync::Arc<T> {
375    #[inline]
376    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
377        (**self).load_any(key, req)
378    }
379}
380
381/// A wrapper over `DynamicDataProvider<AnyMarker>` that implements `AnyProvider`
382#[allow(clippy::exhaustive_structs)] // newtype
383#[derive(Debug)]
384pub struct DynamicDataProviderAnyMarkerWrap<'a, P: ?Sized>(pub &'a P);
385
386/// Blanket-implemented trait adding the [`Self::as_any_provider()`] function.
387pub trait AsDynamicDataProviderAnyMarkerWrap {
388    /// Returns an object implementing `AnyProvider` when called on `DynamicDataProvider<AnyMarker>`
389    fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<Self>;
390}
391
392impl<P> AsDynamicDataProviderAnyMarkerWrap for P
393where
394    P: DynamicDataProvider<AnyMarker> + ?Sized,
395{
396    #[inline]
397    fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<P> {
398        DynamicDataProviderAnyMarkerWrap(self)
399    }
400}
401
402impl<P> AnyProvider for DynamicDataProviderAnyMarkerWrap<'_, P>
403where
404    P: DynamicDataProvider<AnyMarker> + ?Sized,
405{
406    #[inline]
407    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
408        self.0.load_data(key, req)?.try_into()
409    }
410}
411
412/// A wrapper over `AnyProvider` that implements `DynamicDataProvider<M>` via downcasting
413#[allow(clippy::exhaustive_structs)] // newtype
414#[derive(Debug)]
415pub struct DowncastingAnyProvider<'a, P: ?Sized>(pub &'a P);
416
417/// Blanket-implemented trait adding the [`Self::as_downcasting()`] function.
418pub trait AsDowncastingAnyProvider {
419    /// Returns an object implementing `DynamicDataProvider<M>` when called on `AnyProvider`
420    fn as_downcasting(&self) -> DowncastingAnyProvider<Self>;
421}
422
423impl<P> AsDowncastingAnyProvider for P
424where
425    P: AnyProvider + ?Sized,
426{
427    #[inline]
428    fn as_downcasting(&self) -> DowncastingAnyProvider<P> {
429        DowncastingAnyProvider(self)
430    }
431}
432
433impl<M, P> DataProvider<M> for DowncastingAnyProvider<'_, P>
434where
435    P: AnyProvider + ?Sized,
436    M: KeyedDataMarker,
437    for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
438    M::Yokeable: ZeroFrom<'static, M::Yokeable>,
439    M::Yokeable: MaybeSendSync,
440{
441    #[inline]
442    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
443        self.0
444            .load_any(M::KEY, req)?
445            .downcast()
446            .map_err(|e| e.with_req(M::KEY, req))
447    }
448}
449
450impl<M, P> DynamicDataProvider<M> for DowncastingAnyProvider<'_, P>
451where
452    P: AnyProvider + ?Sized,
453    M: DataMarker,
454    for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
455    M::Yokeable: ZeroFrom<'static, M::Yokeable>,
456    M::Yokeable: MaybeSendSync,
457{
458    #[inline]
459    fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> {
460        self.0
461            .load_any(key, req)?
462            .downcast()
463            .map_err(|e| e.with_req(key, req))
464    }
465}
466
467#[cfg(test)]
468mod test {
469    use super::*;
470    use crate::hello_world::*;
471    use alloc::borrow::Cow;
472
473    const CONST_DATA: HelloWorldV1<'static> = HelloWorldV1 {
474        message: Cow::Borrowed("Custom Hello World"),
475    };
476
477    #[test]
478    fn test_debug() {
479        let payload: DataPayload<HelloWorldV1Marker> = DataPayload::from_owned(HelloWorldV1 {
480            message: Cow::Borrowed("Custom Hello World"),
481        });
482
483        let any_payload = payload.wrap_into_any_payload();
484        assert_eq!(
485            "AnyPayload { inner: PayloadRc(Any { .. }), type_name: \"icu_provider::hello_world::HelloWorldV1Marker\" }",
486            format!("{any_payload:?}")
487        );
488
489        struct WrongMarker;
490
491        impl DataMarker for WrongMarker {
492            type Yokeable = u8;
493        }
494
495        let err = any_payload.downcast::<WrongMarker>().unwrap_err();
496        assert_eq!(
497            "ICU4X data error: Mismatched types: tried to downcast with icu_provider::any::test::test_debug::WrongMarker, but actual type is different: icu_provider::hello_world::HelloWorldV1Marker",
498            format!("{err}")
499        );
500    }
501
502    #[test]
503    fn test_non_owned_any_marker() {
504        // This test demonstrates a code path that can trigger the InvalidState error kind.
505        let payload_result: DataPayload<AnyMarker> =
506            DataPayload::from_owned_buffer(Box::new(*b"pretend we're borrowing from here"))
507                .map_project(|_, _| AnyPayload::from_static_ref(&CONST_DATA));
508        let err = payload_result.downcast::<HelloWorldV1Marker>().unwrap_err();
509        assert!(matches!(
510            err,
511            DataError {
512                kind: DataErrorKind::InvalidState,
513                ..
514            }
515        ));
516    }
517}