// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). //! Traits for data providers that produce `Any` objects. use crate::prelude::*; use core::any::Any; use core::convert::TryFrom; use core::convert::TryInto; use yoke::trait_hack::YokeTraitHack; use yoke::Yokeable; use zerofrom::ZeroFrom; #[cfg(not(feature = "sync"))] use alloc::rc::Rc as SelectedRc; #[cfg(feature = "sync")] use alloc::sync::Arc as SelectedRc; /// A trait that allows to specify `Send + Sync` bounds that are only required when /// the `sync` Cargo feature is enabled. Without the Cargo feature, this is an empty bound. #[cfg(feature = "sync")] pub trait MaybeSendSync: Send + Sync {} #[cfg(feature = "sync")] impl MaybeSendSync for T {} #[allow(missing_docs)] // docs generated with all features #[cfg(not(feature = "sync"))] pub trait MaybeSendSync {} #[cfg(not(feature = "sync"))] impl MaybeSendSync for T {} /// Representations of the `Any` trait object. /// /// **Important Note:** The types enclosed by `StructRef` and `PayloadRc` are NOT the same! /// The first refers to the struct itself, whereas the second refers to a `DataPayload`. #[derive(Debug, Clone)] enum AnyPayloadInner { /// A reference to `M::Yokeable` StructRef(&'static dyn Any), /// A boxed `DataPayload`. /// /// Note: This needs to be reference counted, not a `Box`, so that `AnyPayload` is cloneable. /// If an `AnyPayload` is cloned, the actual cloning of the data is delayed until /// `downcast()` is invoked (at which point we have the concrete type). #[cfg(not(feature = "sync"))] PayloadRc(SelectedRc), #[cfg(feature = "sync")] PayloadRc(SelectedRc), } /// A type-erased data payload. /// /// The only useful method on this type is [`AnyPayload::downcast()`], which transforms this into /// a normal `DataPayload` which you can subsequently access or mutate. /// /// As with `DataPayload`, cloning is designed to be cheap. #[derive(Debug, Clone, Yokeable)] pub struct AnyPayload { inner: AnyPayloadInner, type_name: &'static str, } /// The [`DataMarker`] marker type for [`AnyPayload`]. #[allow(clippy::exhaustive_structs)] // marker type pub struct AnyMarker; impl DataMarker for AnyMarker { type Yokeable = AnyPayload; } impl crate::dynutil::UpcastDataPayload for AnyMarker where M: DataMarker + 'static, M::Yokeable: MaybeSendSync, { #[inline] fn upcast(other: DataPayload) -> DataPayload { DataPayload::from_owned(other.wrap_into_any_payload()) } } impl AnyPayload { /// Transforms a type-erased `AnyPayload` into a concrete `DataPayload`. /// /// Because it is expected that the call site knows the identity of the AnyPayload (e.g., from /// the data request), this function returns a `DataError` if the generic type does not match /// the type stored in the `AnyPayload`. pub fn downcast(self) -> Result, DataError> where M: DataMarker + 'static, // For the StructRef case: M::Yokeable: ZeroFrom<'static, M::Yokeable>, // For the PayloadRc case: M::Yokeable: MaybeSendSync, for<'a> YokeTraitHack<>::Output>: Clone, { use AnyPayloadInner::*; let type_name = self.type_name; match self.inner { StructRef(any_ref) => { let down_ref: &'static M::Yokeable = any_ref .downcast_ref() .ok_or_else(|| DataError::for_type::().with_str_context(type_name))?; Ok(DataPayload::from_owned(M::Yokeable::zero_from(down_ref))) } PayloadRc(any_rc) => { let down_rc = any_rc .downcast::>() .map_err(|_| DataError::for_type::().with_str_context(type_name))?; Ok(SelectedRc::try_unwrap(down_rc).unwrap_or_else(|down_rc| (*down_rc).clone())) } } } /// Clones and then transforms a type-erased `AnyPayload` into a concrete `DataPayload`. pub fn downcast_cloned(&self) -> Result, DataError> where M: DataMarker + 'static, // For the StructRef case: M::Yokeable: ZeroFrom<'static, M::Yokeable>, // For the PayloadRc case: M::Yokeable: MaybeSendSync, for<'a> YokeTraitHack<>::Output>: Clone, { self.clone().downcast() } /// Creates an `AnyPayload` from a static reference to a data struct. /// /// # Examples /// /// ``` /// use icu_provider::hello_world::*; /// use icu_provider::prelude::*; /// use std::borrow::Cow; /// /// const HELLO_DATA: HelloWorldV1<'static> = HelloWorldV1 { /// message: Cow::Borrowed("Custom Hello World"), /// }; /// /// let any_payload = AnyPayload::from_static_ref(&HELLO_DATA); /// /// let payload: DataPayload = /// any_payload.downcast().expect("TypeId matches"); /// assert_eq!("Custom Hello World", payload.get().message); /// ``` pub fn from_static_ref(static_ref: &'static Y) -> Self where Y: for<'a> Yokeable<'a>, { AnyPayload { inner: AnyPayloadInner::StructRef(static_ref), // Note: This records the Yokeable type rather than the DataMarker type, // but that is okay since this is only for debugging type_name: core::any::type_name::(), } } } impl DataPayload where M: DataMarker + 'static, M::Yokeable: MaybeSendSync, { /// Moves this DataPayload to the heap (requiring an allocation) and returns it as an /// erased `AnyPayload`. /// /// # Examples /// /// ``` /// use icu_provider::hello_world::*; /// use icu_provider::prelude::*; /// use std::borrow::Cow; /// use std::rc::Rc; /// /// let payload: DataPayload = /// DataPayload::from_owned(HelloWorldV1 { /// message: Cow::Borrowed("Custom Hello World"), /// }); /// /// let any_payload = payload.wrap_into_any_payload(); /// /// let payload: DataPayload = /// any_payload.downcast().expect("TypeId matches"); /// assert_eq!("Custom Hello World", payload.get().message); /// ``` pub fn wrap_into_any_payload(self) -> AnyPayload { AnyPayload { inner: AnyPayloadInner::PayloadRc(SelectedRc::from(self)), type_name: core::any::type_name::(), } } } impl DataPayload { /// Transforms a type-erased `DataPayload` into a concrete `DataPayload`. #[inline] pub fn downcast(self) -> Result, DataError> where M: DataMarker + 'static, for<'a> YokeTraitHack<>::Output>: Clone, M::Yokeable: ZeroFrom<'static, M::Yokeable>, M::Yokeable: MaybeSendSync, { self.try_unwrap_owned()?.downcast() } } /// A [`DataResponse`] for type-erased values. /// /// Convertible to and from `DataResponse`. #[allow(clippy::exhaustive_structs)] // this type is stable (the metadata is allowed to grow) pub struct AnyResponse { /// Metadata about the returned object. pub metadata: DataResponseMetadata, /// The object itself; None if it was not loaded. pub payload: Option, } impl TryFrom> for AnyResponse { type Error = DataError; #[inline] fn try_from(other: DataResponse) -> Result { Ok(Self { metadata: other.metadata, payload: other.payload.map(|p| p.try_unwrap_owned()).transpose()?, }) } } impl From for DataResponse { #[inline] fn from(other: AnyResponse) -> Self { Self { metadata: other.metadata, payload: other.payload.map(DataPayload::from_owned), } } } impl AnyResponse { /// Transforms a type-erased `AnyResponse` into a concrete `DataResponse`. #[inline] pub fn downcast(self) -> Result, DataError> where M: DataMarker + 'static, for<'a> YokeTraitHack<>::Output>: Clone, M::Yokeable: ZeroFrom<'static, M::Yokeable>, M::Yokeable: MaybeSendSync, { Ok(DataResponse { metadata: self.metadata, payload: self.payload.map(|p| p.downcast()).transpose()?, }) } /// Clones and then transforms a type-erased `AnyResponse` into a concrete `DataResponse`. pub fn downcast_cloned(&self) -> Result, DataError> where M: DataMarker + 'static, M::Yokeable: ZeroFrom<'static, M::Yokeable>, M::Yokeable: MaybeSendSync, for<'a> YokeTraitHack<>::Output>: Clone, { Ok(DataResponse { metadata: self.metadata.clone(), payload: self .payload .as_ref() .map(|p| p.downcast_cloned()) .transpose()?, }) } } impl DataResponse where M: DataMarker + 'static, M::Yokeable: MaybeSendSync, { /// Moves the inner DataPayload to the heap (requiring an allocation) and returns it as an /// erased `AnyResponse`. pub fn wrap_into_any_response(self) -> AnyResponse { AnyResponse { metadata: self.metadata, payload: self.payload.map(|p| p.wrap_into_any_payload()), } } } /// An object-safe data provider that returns data structs cast to `dyn Any` trait objects. /// /// # Examples /// /// ``` /// use icu_provider::hello_world::*; /// use icu_provider::prelude::*; /// use std::borrow::Cow; /// /// let any_response = HelloWorldProvider /// .as_any_provider() /// .load_any( /// HelloWorldV1Marker::KEY, /// DataRequest { /// locale: &icu_locid::locale!("de").into(), /// metadata: Default::default(), /// }, /// ) /// .expect("Load should succeed"); /// /// // Downcast to something useful /// let response: DataResponse = /// any_response.downcast().expect("Types match"); /// /// let payload = response.take_payload().expect("Data should be present"); /// /// assert_eq!(payload.get().message, "Hallo Welt"); /// ``` pub trait AnyProvider { /// Loads an [`AnyPayload`] according to the key and request. fn load_any(&self, key: DataKey, req: DataRequest) -> Result; } impl AnyProvider for alloc::boxed::Box { fn load_any(&self, key: DataKey, req: DataRequest) -> Result { (**self).load_any(key, req) } } /// A wrapper over `DynamicDataProvider` that implements `AnyProvider` #[allow(clippy::exhaustive_structs)] // newtype pub struct DynamicDataProviderAnyMarkerWrap<'a, P: ?Sized>(pub &'a P); /// Blanket-implemented trait adding the [`Self::as_any_provider()`] function. pub trait AsDynamicDataProviderAnyMarkerWrap { /// Returns an object implementing `AnyProvider` when called on `DynamicDataProvider` fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap; } impl

AsDynamicDataProviderAnyMarkerWrap for P where P: DynamicDataProvider, { #[inline] fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap

{ DynamicDataProviderAnyMarkerWrap(self) } } impl

AnyProvider for DynamicDataProviderAnyMarkerWrap<'_, P> where P: DynamicDataProvider + ?Sized, { #[inline] fn load_any(&self, key: DataKey, req: DataRequest) -> Result { self.0.load_data(key, req)?.try_into() } } /// A wrapper over `AnyProvider` that implements `DynamicDataProvider` via downcasting #[allow(clippy::exhaustive_structs)] // newtype pub struct DowncastingAnyProvider<'a, P: ?Sized>(pub &'a P); /// Blanket-implemented trait adding the [`Self::as_downcasting()`] function. pub trait AsDowncastingAnyProvider { /// Returns an object implementing `DynamicDataProvider` when called on `AnyProvider` fn as_downcasting(&self) -> DowncastingAnyProvider; } impl

AsDowncastingAnyProvider for P where P: AnyProvider + ?Sized, { #[inline] fn as_downcasting(&self) -> DowncastingAnyProvider

{ DowncastingAnyProvider(self) } } impl DataProvider for DowncastingAnyProvider<'_, P> where P: AnyProvider + ?Sized, M: KeyedDataMarker + 'static, for<'a> YokeTraitHack<>::Output>: Clone, M::Yokeable: ZeroFrom<'static, M::Yokeable>, M::Yokeable: MaybeSendSync, { #[inline] fn load(&self, req: DataRequest) -> Result, DataError> { self.0 .load_any(M::KEY, req)? .downcast() .map_err(|e| e.with_req(M::KEY, req)) } } impl DynamicDataProvider for DowncastingAnyProvider<'_, P> where P: AnyProvider + ?Sized, M: DataMarker + 'static, for<'a> YokeTraitHack<>::Output>: Clone, M::Yokeable: ZeroFrom<'static, M::Yokeable>, M::Yokeable: MaybeSendSync, { #[inline] fn load_data(&self, key: DataKey, req: DataRequest) -> Result, DataError> { self.0 .load_any(key, req)? .downcast() .map_err(|e| e.with_req(key, req)) } } #[cfg(test)] mod test { use super::*; use crate::hello_world::*; use alloc::borrow::Cow; const CONST_DATA: HelloWorldV1<'static> = HelloWorldV1 { message: Cow::Borrowed("Custom Hello World"), }; #[test] fn test_debug() { let payload: DataPayload = DataPayload::from_owned(HelloWorldV1 { message: Cow::Borrowed("Custom Hello World"), }); let any_payload = payload.wrap_into_any_payload(); assert_eq!( "AnyPayload { inner: PayloadRc(Any { .. }), type_name: \"icu_provider::hello_world::HelloWorldV1Marker\" }", format!("{:?}", any_payload) ); struct WrongMarker; impl DataMarker for WrongMarker { type Yokeable = u8; } let err = any_payload.downcast::().unwrap_err(); assert_eq!( "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", format!("{}", err) ); } #[test] fn test_non_owned_any_marker() { // This test demonstrates a code path that can trigger the InvalidState error kind. let payload_result: DataPayload = DataPayload::from_owned_buffer(Box::new(*b"pretend we're borrowing from here")) .map_project(|_, _| AnyPayload::from_static_ref(&CONST_DATA)); let err = payload_result.downcast::().unwrap_err(); assert!(matches!( err, DataError { kind: DataErrorKind::InvalidState, .. } )); } }