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(data: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 /// use std::rc::Rc;
180 ///
181 /// let payload: DataPayload<HelloWorldV1Marker> =
182 /// DataPayload::from_owned(HelloWorldV1 {
183 /// message: Cow::Borrowed("Custom Hello World"),
184 /// });
185 ///
186 /// let any_payload = payload.wrap_into_any_payload();
187 ///
188 /// let payload: DataPayload<HelloWorldV1Marker> =
189 /// any_payload.downcast().expect("TypeId matches");
190 /// assert_eq!("Custom Hello World", payload.get().message);
191 /// ```
192 pub fn wrap_into_any_payload(self) -> AnyPayload {
193 AnyPayload {
194 inner: match self.0 {
195 DataPayloadInner::StaticRef(r) => AnyPayloadInner::StructRef(r),
196 inner => AnyPayloadInner::PayloadRc(SelectedRc::from(Self(inner))),
197 },
198 type_name: core::any::type_name::<M>(),
199 }
200 }
201}
202
203impl DataPayload<AnyMarker> {
204 /// Transforms a type-erased `DataPayload<AnyMarker>` into a concrete `DataPayload<M>`.
205 #[inline]
206 pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
207 where
208 M: DataMarker,
209 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
210 M::Yokeable: ZeroFrom<'static, M::Yokeable>,
211 M::Yokeable: MaybeSendSync,
212 {
213 self.try_unwrap_owned()?.downcast()
214 }
215}
216
217/// A [`DataResponse`] for type-erased values.
218///
219/// Convertible to and from `DataResponse<AnyMarker>`.
220#[allow(clippy::exhaustive_structs)] // this type is stable (the metadata is allowed to grow)
221#[derive(Debug)]
222pub struct AnyResponse {
223 /// Metadata about the returned object.
224 pub metadata: DataResponseMetadata,
225
226 /// The object itself; `None` if it was not loaded.
227 pub payload: Option<AnyPayload>,
228}
229
230impl TryFrom<DataResponse<AnyMarker>> for AnyResponse {
231 type Error = DataError;
232 #[inline]
233 fn try_from(other: DataResponse<AnyMarker>) -> Result<Self, Self::Error> {
234 Ok(Self {
235 metadata: other.metadata,
236 payload: other.payload.map(|p: DataPayload| p.try_unwrap_owned()).transpose()?,
237 })
238 }
239}
240
241impl From<AnyResponse> for DataResponse<AnyMarker> {
242 #[inline]
243 fn from(other: AnyResponse) -> Self {
244 Self {
245 metadata: other.metadata,
246 payload: other.payload.map(DataPayload::from_owned),
247 }
248 }
249}
250
251impl AnyResponse {
252 /// Transforms a type-erased `AnyResponse` into a concrete `DataResponse<M>`.
253 #[inline]
254 pub fn downcast<M>(self) -> Result<DataResponse<M>, DataError>
255 where
256 M: DataMarker,
257 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
258 M::Yokeable: ZeroFrom<'static, M::Yokeable>,
259 M::Yokeable: MaybeSendSync,
260 {
261 Ok(DataResponse {
262 metadata: self.metadata,
263 payload: self.payload.map(|p| p.downcast()).transpose()?,
264 })
265 }
266
267 /// Clones and then transforms a type-erased `AnyResponse` into a concrete `DataResponse<M>`.
268 pub fn downcast_cloned<M>(&self) -> Result<DataResponse<M>, DataError>
269 where
270 M: DataMarker,
271 M::Yokeable: ZeroFrom<'static, M::Yokeable>,
272 M::Yokeable: MaybeSendSync,
273 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
274 {
275 Ok(DataResponse {
276 metadata: self.metadata.clone(),
277 payload: self
278 .payload
279 .as_ref()
280 .map(|p| p.downcast_cloned())
281 .transpose()?,
282 })
283 }
284}
285
286impl<M> DataResponse<M>
287where
288 M: DataMarker,
289 M::Yokeable: MaybeSendSync,
290{
291 /// Moves the inner DataPayload to the heap (requiring an allocation) and returns it as an
292 /// erased `AnyResponse`.
293 pub fn wrap_into_any_response(self) -> AnyResponse {
294 AnyResponse {
295 metadata: self.metadata,
296 payload: self.payload.map(|p: DataPayload| p.wrap_into_any_payload()),
297 }
298 }
299}
300
301/// An object-safe data provider that returns data structs cast to `dyn Any` trait objects.
302///
303/// # Examples
304///
305/// ```
306/// use icu_provider::hello_world::*;
307/// use icu_provider::prelude::*;
308/// use std::borrow::Cow;
309///
310/// let any_provider = HelloWorldProvider.as_any_provider();
311///
312/// let req = DataRequest {
313/// locale: &icu_locid::locale!("de").into(),
314/// metadata: Default::default(),
315/// };
316///
317/// // Downcasting manually
318/// assert_eq!(
319/// any_provider
320/// .load_any(HelloWorldV1Marker::KEY, req)
321/// .expect("load should succeed")
322/// .downcast::<HelloWorldV1Marker>()
323/// .expect("types should match")
324/// .take_payload()
325/// .unwrap()
326/// .get(),
327/// &HelloWorldV1 {
328/// message: Cow::Borrowed("Hallo Welt"),
329/// },
330/// );
331///
332/// // Downcasting automatically
333/// let downcasting_provider: &dyn DataProvider<HelloWorldV1Marker> =
334/// &any_provider.as_downcasting();
335///
336/// assert_eq!(
337/// downcasting_provider
338/// .load(req)
339/// .expect("load should succeed")
340/// .take_payload()
341/// .unwrap()
342/// .get(),
343/// &HelloWorldV1 {
344/// message: Cow::Borrowed("Hallo Welt"),
345/// },
346/// );
347/// ```
348pub trait AnyProvider {
349 /// Loads an [`AnyPayload`] according to the key and request.
350 fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError>;
351}
352
353impl<T: AnyProvider + ?Sized> AnyProvider for alloc::boxed::Box<T> {
354 fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
355 (**self).load_any(key, req)
356 }
357}
358
359/// A wrapper over `DynamicDataProvider<AnyMarker>` that implements `AnyProvider`
360#[allow(clippy::exhaustive_structs)] // newtype
361#[derive(Debug)]
362pub struct DynamicDataProviderAnyMarkerWrap<'a, P: ?Sized>(pub &'a P);
363
364/// Blanket-implemented trait adding the [`Self::as_any_provider()`] function.
365pub trait AsDynamicDataProviderAnyMarkerWrap {
366 /// Returns an object implementing `AnyProvider` when called on `DynamicDataProvider<AnyMarker>`
367 fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<Self>;
368}
369
370impl<P> AsDynamicDataProviderAnyMarkerWrap for P
371where
372 P: DynamicDataProvider<AnyMarker> + ?Sized,
373{
374 #[inline]
375 fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<P> {
376 DynamicDataProviderAnyMarkerWrap(self)
377 }
378}
379
380impl<P> AnyProvider for DynamicDataProviderAnyMarkerWrap<'_, P>
381where
382 P: DynamicDataProvider<AnyMarker> + ?Sized,
383{
384 #[inline]
385 fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
386 self.0.load_data(key, req)?.try_into()
387 }
388}
389
390/// A wrapper over `AnyProvider` that implements `DynamicDataProvider<M>` via downcasting
391#[allow(clippy::exhaustive_structs)] // newtype
392#[derive(Debug)]
393pub struct DowncastingAnyProvider<'a, P: ?Sized>(pub &'a P);
394
395/// Blanket-implemented trait adding the [`Self::as_downcasting()`] function.
396pub trait AsDowncastingAnyProvider {
397 /// Returns an object implementing `DynamicDataProvider<M>` when called on `AnyProvider`
398 fn as_downcasting(&self) -> DowncastingAnyProvider<Self>;
399}
400
401impl<P> AsDowncastingAnyProvider for P
402where
403 P: AnyProvider + ?Sized,
404{
405 #[inline]
406 fn as_downcasting(&self) -> DowncastingAnyProvider<P> {
407 DowncastingAnyProvider(self)
408 }
409}
410
411impl<M, P> DataProvider<M> for DowncastingAnyProvider<'_, P>
412where
413 P: AnyProvider + ?Sized,
414 M: KeyedDataMarker,
415 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
416 M::Yokeable: ZeroFrom<'static, M::Yokeable>,
417 M::Yokeable: MaybeSendSync,
418{
419 #[inline]
420 fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
421 self.0
422 .load_any(M::KEY, req)?
423 .downcast()
424 .map_err(|e: DataError| e.with_req(M::KEY, req))
425 }
426}
427
428impl<M, P> DynamicDataProvider<M> for DowncastingAnyProvider<'_, P>
429where
430 P: AnyProvider + ?Sized,
431 M: DataMarker,
432 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
433 M::Yokeable: ZeroFrom<'static, M::Yokeable>,
434 M::Yokeable: MaybeSendSync,
435{
436 #[inline]
437 fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> {
438 self.0
439 .load_any(key, req)?
440 .downcast()
441 .map_err(|e: DataError| e.with_req(key, req))
442 }
443}
444
445#[cfg(test)]
446mod test {
447 use super::*;
448 use crate::hello_world::*;
449 use alloc::borrow::Cow;
450
451 const CONST_DATA: HelloWorldV1<'static> = HelloWorldV1 {
452 message: Cow::Borrowed("Custom Hello World"),
453 };
454
455 #[test]
456 fn test_debug() {
457 let payload: DataPayload<HelloWorldV1Marker> = DataPayload::from_owned(HelloWorldV1 {
458 message: Cow::Borrowed("Custom Hello World"),
459 });
460
461 let any_payload = payload.wrap_into_any_payload();
462 assert_eq!(
463 "AnyPayload { inner: PayloadRc(Any { .. }), type_name: \"icu_provider::hello_world::HelloWorldV1Marker\" }",
464 format!("{any_payload:?}")
465 );
466
467 struct WrongMarker;
468
469 impl DataMarker for WrongMarker {
470 type Yokeable = u8;
471 }
472
473 let err = any_payload.downcast::<WrongMarker>().unwrap_err();
474 assert_eq!(
475 "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",
476 format!("{err}")
477 );
478 }
479
480 #[test]
481 fn test_non_owned_any_marker() {
482 // This test demonstrates a code path that can trigger the InvalidState error kind.
483 let payload_result: DataPayload<AnyMarker> =
484 DataPayload::from_owned_buffer(Box::new(*b"pretend we're borrowing from here"))
485 .map_project(|_, _| AnyPayload::from_static_ref(&CONST_DATA));
486 let err = payload_result.downcast::<HelloWorldV1Marker>().unwrap_err();
487 assert!(matches!(
488 err,
489 DataError {
490 kind: DataErrorKind::InvalidState,
491 ..
492 }
493 ));
494 }
495}
496