use crate::prelude::*;
use crate::response::DataPayloadInner;
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;
#[cfg(feature = "sync")]
pub trait MaybeSendSync: Send + Sync {}
#[cfg(feature = "sync")]
impl<T: Send + Sync> MaybeSendSync for T {}
#[allow(missing_docs)] #[cfg(not(feature = "sync"))]
pub trait MaybeSendSync {}
#[cfg(not(feature = "sync"))]
impl<T> MaybeSendSync for T {}
#[derive(Debug, Clone)]
enum AnyPayloadInner {
StructRef(&'static dyn Any),
#[cfg(not(feature = "sync"))]
PayloadRc(SelectedRc<dyn Any>),
#[cfg(feature = "sync")]
PayloadRc(SelectedRc<dyn Any + Send + Sync>),
}
#[derive(Debug, Clone, Yokeable)]
pub struct AnyPayload {
inner: AnyPayloadInner,
type_name: &'static str,
}
#[allow(clippy::exhaustive_structs)] #[derive(Debug)]
pub struct AnyMarker;
impl DataMarker for AnyMarker {
type Yokeable = AnyPayload;
}
impl<M> crate::dynutil::UpcastDataPayload<M> for AnyMarker
where
M: DataMarker,
M::Yokeable: MaybeSendSync,
{
#[inline]
fn upcast(other: DataPayload<M>) -> DataPayload<AnyMarker> {
DataPayload::from_owned(other.wrap_into_any_payload())
}
}
impl AnyPayload {
pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
where
M: DataMarker,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::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::<M>().with_str_context(type_name))?;
Ok(DataPayload::from_static_ref(down_ref))
}
PayloadRc(any_rc) => {
let down_rc = any_rc
.downcast::<DataPayload<M>>()
.map_err(|_| DataError::for_type::<M>().with_str_context(type_name))?;
Ok(SelectedRc::try_unwrap(down_rc).unwrap_or_else(|down_rc| (*down_rc).clone()))
}
}
}
pub fn downcast_cloned<M>(&self) -> Result<DataPayload<M>, DataError>
where
M: DataMarker,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
{
self.clone().downcast()
}
pub fn from_static_ref<Y>(static_ref: &'static Y) -> Self
where
Y: for<'a> Yokeable<'a>,
{
AnyPayload {
inner: AnyPayloadInner::StructRef(static_ref),
type_name: core::any::type_name::<Y>(),
}
}
}
impl<M> DataPayload<M>
where
M: DataMarker,
M::Yokeable: MaybeSendSync,
{
pub fn wrap_into_any_payload(self) -> AnyPayload {
AnyPayload {
inner: match self.0 {
DataPayloadInner::StaticRef(r) => AnyPayloadInner::StructRef(r),
inner => AnyPayloadInner::PayloadRc(SelectedRc::from(Self(inner))),
},
type_name: core::any::type_name::<M>(),
}
}
}
impl DataPayload<AnyMarker> {
#[inline]
pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
where
M: DataMarker,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
{
self.try_unwrap_owned()?.downcast()
}
}
#[allow(clippy::exhaustive_structs)] #[derive(Debug)]
pub struct AnyResponse {
pub metadata: DataResponseMetadata,
pub payload: Option<AnyPayload>,
}
impl TryFrom<DataResponse<AnyMarker>> for AnyResponse {
type Error = DataError;
#[inline]
fn try_from(other: DataResponse<AnyMarker>) -> Result<Self, Self::Error> {
Ok(Self {
metadata: other.metadata,
payload: other.payload.map(|p| p.try_unwrap_owned()).transpose()?,
})
}
}
impl From<AnyResponse> for DataResponse<AnyMarker> {
#[inline]
fn from(other: AnyResponse) -> Self {
Self {
metadata: other.metadata,
payload: other.payload.map(DataPayload::from_owned),
}
}
}
impl AnyResponse {
#[inline]
pub fn downcast<M>(self) -> Result<DataResponse<M>, DataError>
where
M: DataMarker,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
{
Ok(DataResponse {
metadata: self.metadata,
payload: self.payload.map(|p| p.downcast()).transpose()?,
})
}
pub fn downcast_cloned<M>(&self) -> Result<DataResponse<M>, DataError>
where
M: DataMarker,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
{
Ok(DataResponse {
metadata: self.metadata.clone(),
payload: self
.payload
.as_ref()
.map(|p| p.downcast_cloned())
.transpose()?,
})
}
}
impl<M> DataResponse<M>
where
M: DataMarker,
M::Yokeable: MaybeSendSync,
{
pub fn wrap_into_any_response(self) -> AnyResponse {
AnyResponse {
metadata: self.metadata,
payload: self.payload.map(|p| p.wrap_into_any_payload()),
}
}
}
pub trait AnyProvider {
fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError>;
}
impl<T: AnyProvider + ?Sized> AnyProvider for alloc::boxed::Box<T> {
fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
(**self).load_any(key, req)
}
}
#[allow(clippy::exhaustive_structs)] #[derive(Debug)]
pub struct DynamicDataProviderAnyMarkerWrap<'a, P: ?Sized>(pub &'a P);
pub trait AsDynamicDataProviderAnyMarkerWrap {
fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<Self>;
}
impl<P> AsDynamicDataProviderAnyMarkerWrap for P
where
P: DynamicDataProvider<AnyMarker> + ?Sized,
{
#[inline]
fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<P> {
DynamicDataProviderAnyMarkerWrap(self)
}
}
impl<P> AnyProvider for DynamicDataProviderAnyMarkerWrap<'_, P>
where
P: DynamicDataProvider<AnyMarker> + ?Sized,
{
#[inline]
fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
self.0.load_data(key, req)?.try_into()
}
}
#[allow(clippy::exhaustive_structs)] #[derive(Debug)]
pub struct DowncastingAnyProvider<'a, P: ?Sized>(pub &'a P);
pub trait AsDowncastingAnyProvider {
fn as_downcasting(&self) -> DowncastingAnyProvider<Self>;
}
impl<P> AsDowncastingAnyProvider for P
where
P: AnyProvider + ?Sized,
{
#[inline]
fn as_downcasting(&self) -> DowncastingAnyProvider<P> {
DowncastingAnyProvider(self)
}
}
impl<M, P> DataProvider<M> for DowncastingAnyProvider<'_, P>
where
P: AnyProvider + ?Sized,
M: KeyedDataMarker,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
{
#[inline]
fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
self.0
.load_any(M::KEY, req)?
.downcast()
.map_err(|e| e.with_req(M::KEY, req))
}
}
impl<M, P> DynamicDataProvider<M> for DowncastingAnyProvider<'_, P>
where
P: AnyProvider + ?Sized,
M: DataMarker,
for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
M::Yokeable: ZeroFrom<'static, M::Yokeable>,
M::Yokeable: MaybeSendSync,
{
#[inline]
fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, 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<HelloWorldV1Marker> = 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::<WrongMarker>().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() {
let payload_result: DataPayload<AnyMarker> =
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::<HelloWorldV1Marker>().unwrap_err();
assert!(matches!(
err,
DataError {
kind: DataErrorKind::InvalidState,
..
}
));
}
}