Coverage Report

Created: 2025-05-07 06:59

/rust/registry/src/index.crates.io-6f17d22bba15001f/icu_provider-1.5.0/src/any.rs
Line
Count
Source (jump to first uncovered line)
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
7
use crate::prelude::*;
8
use crate::response::DataPayloadInner;
9
use core::any::Any;
10
use core::convert::TryFrom;
11
use core::convert::TryInto;
12
use yoke::trait_hack::YokeTraitHack;
13
use yoke::Yokeable;
14
use zerofrom::ZeroFrom;
15
16
#[cfg(not(feature = "sync"))]
17
use alloc::rc::Rc as SelectedRc;
18
#[cfg(feature = "sync")]
19
use 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")]
24
pub trait MaybeSendSync: Send + Sync {}
25
#[cfg(feature = "sync")]
26
impl<T: Send + Sync> MaybeSendSync for T {}
27
28
#[allow(missing_docs)] // docs generated with all features
29
#[cfg(not(feature = "sync"))]
30
pub trait MaybeSendSync {}
31
#[cfg(not(feature = "sync"))]
32
impl<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)]
39
enum 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
0
#[derive(Debug, Clone, Yokeable)]
Unexecuted instantiation: <icu_provider::any::AnyPayload as yoke::yokeable::Yokeable>::transform
Unexecuted instantiation: <icu_provider::any::AnyPayload as yoke::yokeable::Yokeable>::transform_owned
Unexecuted instantiation: <icu_provider::any::AnyPayload as yoke::yokeable::Yokeable>::make
Unexecuted instantiation: <icu_provider::any::AnyPayload as yoke::yokeable::Yokeable>::transform_mut::<_>
62
pub 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)]
70
pub struct AnyMarker;
71
72
impl DataMarker for AnyMarker {
73
    type Yokeable = AnyPayload;
74
}
75
76
impl<M> crate::dynutil::UpcastDataPayload<M> for AnyMarker
77
where
78
    M: DataMarker,
79
    M::Yokeable: MaybeSendSync,
80
{
81
    #[inline]
82
0
    fn upcast(other: DataPayload<M>) -> DataPayload<AnyMarker> {
83
0
        DataPayload::from_owned(other.wrap_into_any_payload())
84
0
    }
85
}
86
87
impl 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
0
    pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
94
0
    where
95
0
        M: DataMarker,
96
0
        // For the StructRef case:
97
0
        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
98
0
        // For the PayloadRc case:
99
0
        M::Yokeable: MaybeSendSync,
100
0
        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
101
0
    {
102
        use AnyPayloadInner::*;
103
0
        let type_name = self.type_name;
104
0
        match self.inner {
105
0
            StructRef(any_ref) => {
106
0
                let down_ref: &'static M::Yokeable = any_ref
107
0
                    .downcast_ref()
108
0
                    .ok_or_else(|| DataError::for_type::<M>().with_str_context(type_name))?;
109
0
                Ok(DataPayload::from_static_ref(down_ref))
110
            }
111
0
            PayloadRc(any_rc) => {
112
0
                let down_rc = any_rc
113
0
                    .downcast::<DataPayload<M>>()
114
0
                    .map_err(|_| DataError::for_type::<M>().with_str_context(type_name))?;
115
0
                Ok(SelectedRc::try_unwrap(down_rc).unwrap_or_else(|down_rc| (*down_rc).clone()))
116
            }
117
        }
118
0
    }
119
120
    /// Clones and then transforms a type-erased `AnyPayload` into a concrete `DataPayload<M>`.
121
0
    pub fn downcast_cloned<M>(&self) -> Result<DataPayload<M>, DataError>
122
0
    where
123
0
        M: DataMarker,
124
0
        // For the StructRef case:
125
0
        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
126
0
        // For the PayloadRc case:
127
0
        M::Yokeable: MaybeSendSync,
128
0
        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
129
0
    {
130
0
        self.clone().downcast()
131
0
    }
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
0
    pub fn from_static_ref<Y>(static_ref: &'static Y) -> Self
153
0
    where
154
0
        Y: for<'a> Yokeable<'a>,
155
0
    {
156
0
        AnyPayload {
157
0
            inner: AnyPayloadInner::StructRef(static_ref),
158
0
            // Note: This records the Yokeable type rather than the DataMarker type,
159
0
            // but that is okay since this is only for debugging
160
0
            type_name: core::any::type_name::<Y>(),
161
0
        }
162
0
    }
163
}
164
165
impl<M> DataPayload<M>
166
where
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
0
    pub fn wrap_into_any_payload(self) -> AnyPayload {
192
0
        AnyPayload {
193
0
            inner: match self.0 {
194
0
                DataPayloadInner::StaticRef(r) => AnyPayloadInner::StructRef(r),
195
0
                inner => AnyPayloadInner::PayloadRc(SelectedRc::from(Self(inner))),
196
            },
197
0
            type_name: core::any::type_name::<M>(),
198
0
        }
199
0
    }
200
}
201
202
impl DataPayload<AnyMarker> {
203
    /// Transforms a type-erased `DataPayload<AnyMarker>` into a concrete `DataPayload<M>`.
204
    #[inline]
205
0
    pub fn downcast<M>(self) -> Result<DataPayload<M>, DataError>
206
0
    where
207
0
        M: DataMarker,
208
0
        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
209
0
        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
210
0
        M::Yokeable: MaybeSendSync,
211
0
    {
212
0
        self.try_unwrap_owned()?.downcast()
213
0
    }
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)]
221
pub 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
229
impl TryFrom<DataResponse<AnyMarker>> for AnyResponse {
230
    type Error = DataError;
231
    #[inline]
232
0
    fn try_from(other: DataResponse<AnyMarker>) -> Result<Self, Self::Error> {
233
0
        Ok(Self {
234
0
            metadata: other.metadata,
235
0
            payload: other.payload.map(|p| p.try_unwrap_owned()).transpose()?,
236
        })
237
0
    }
238
}
239
240
impl From<AnyResponse> for DataResponse<AnyMarker> {
241
    #[inline]
242
0
    fn from(other: AnyResponse) -> Self {
243
0
        Self {
244
0
            metadata: other.metadata,
245
0
            payload: other.payload.map(DataPayload::from_owned),
246
0
        }
247
0
    }
248
}
249
250
impl AnyResponse {
251
    /// Transforms a type-erased `AnyResponse` into a concrete `DataResponse<M>`.
252
    #[inline]
253
0
    pub fn downcast<M>(self) -> Result<DataResponse<M>, DataError>
254
0
    where
255
0
        M: DataMarker,
256
0
        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
257
0
        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
258
0
        M::Yokeable: MaybeSendSync,
259
0
    {
260
0
        Ok(DataResponse {
261
0
            metadata: self.metadata,
262
0
            payload: self.payload.map(|p| p.downcast()).transpose()?,
263
        })
264
0
    }
265
266
    /// Clones and then transforms a type-erased `AnyResponse` into a concrete `DataResponse<M>`.
267
0
    pub fn downcast_cloned<M>(&self) -> Result<DataResponse<M>, DataError>
268
0
    where
269
0
        M: DataMarker,
270
0
        M::Yokeable: ZeroFrom<'static, M::Yokeable>,
271
0
        M::Yokeable: MaybeSendSync,
272
0
        for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
273
0
    {
274
0
        Ok(DataResponse {
275
0
            metadata: self.metadata.clone(),
276
0
            payload: self
277
0
                .payload
278
0
                .as_ref()
279
0
                .map(|p| p.downcast_cloned())
280
0
                .transpose()?,
281
        })
282
0
    }
283
}
284
285
impl<M> DataResponse<M>
286
where
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
0
    pub fn wrap_into_any_response(self) -> AnyResponse {
293
0
        AnyResponse {
294
0
            metadata: self.metadata,
295
0
            payload: self.payload.map(|p| p.wrap_into_any_payload()),
296
0
        }
297
0
    }
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
/// ```
347
pub 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
352
impl<'a, T: AnyProvider + ?Sized> AnyProvider for &'a T {
353
    #[inline]
354
0
    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
355
0
        (**self).load_any(key, req)
356
0
    }
357
}
358
359
impl<T: AnyProvider + ?Sized> AnyProvider for alloc::boxed::Box<T> {
360
    #[inline]
361
0
    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
362
0
        (**self).load_any(key, req)
363
0
    }
364
}
365
366
impl<T: AnyProvider + ?Sized> AnyProvider for alloc::rc::Rc<T> {
367
    #[inline]
368
0
    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
369
0
        (**self).load_any(key, req)
370
0
    }
371
}
372
373
#[cfg(target_has_atomic = "ptr")]
374
impl<T: AnyProvider + ?Sized> AnyProvider for alloc::sync::Arc<T> {
375
    #[inline]
376
0
    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
377
0
        (**self).load_any(key, req)
378
0
    }
379
}
380
381
/// A wrapper over `DynamicDataProvider<AnyMarker>` that implements `AnyProvider`
382
#[allow(clippy::exhaustive_structs)] // newtype
383
#[derive(Debug)]
384
pub struct DynamicDataProviderAnyMarkerWrap<'a, P: ?Sized>(pub &'a P);
385
386
/// Blanket-implemented trait adding the [`Self::as_any_provider()`] function.
387
pub trait AsDynamicDataProviderAnyMarkerWrap {
388
    /// Returns an object implementing `AnyProvider` when called on `DynamicDataProvider<AnyMarker>`
389
    fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<Self>;
390
}
391
392
impl<P> AsDynamicDataProviderAnyMarkerWrap for P
393
where
394
    P: DynamicDataProvider<AnyMarker> + ?Sized,
395
{
396
    #[inline]
397
0
    fn as_any_provider(&self) -> DynamicDataProviderAnyMarkerWrap<P> {
398
0
        DynamicDataProviderAnyMarkerWrap(self)
399
0
    }
400
}
401
402
impl<P> AnyProvider for DynamicDataProviderAnyMarkerWrap<'_, P>
403
where
404
    P: DynamicDataProvider<AnyMarker> + ?Sized,
405
{
406
    #[inline]
407
0
    fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> {
408
0
        self.0.load_data(key, req)?.try_into()
409
0
    }
410
}
411
412
/// A wrapper over `AnyProvider` that implements `DynamicDataProvider<M>` via downcasting
413
#[allow(clippy::exhaustive_structs)] // newtype
414
#[derive(Debug)]
415
pub struct DowncastingAnyProvider<'a, P: ?Sized>(pub &'a P);
416
417
/// Blanket-implemented trait adding the [`Self::as_downcasting()`] function.
418
pub trait AsDowncastingAnyProvider {
419
    /// Returns an object implementing `DynamicDataProvider<M>` when called on `AnyProvider`
420
    fn as_downcasting(&self) -> DowncastingAnyProvider<Self>;
421
}
422
423
impl<P> AsDowncastingAnyProvider for P
424
where
425
    P: AnyProvider + ?Sized,
426
{
427
    #[inline]
428
0
    fn as_downcasting(&self) -> DowncastingAnyProvider<P> {
429
0
        DowncastingAnyProvider(self)
430
0
    }
431
}
432
433
impl<M, P> DataProvider<M> for DowncastingAnyProvider<'_, P>
434
where
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
0
    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
443
0
        self.0
444
0
            .load_any(M::KEY, req)?
445
0
            .downcast()
446
0
            .map_err(|e| e.with_req(M::KEY, req))
447
0
    }
448
}
449
450
impl<M, P> DynamicDataProvider<M> for DowncastingAnyProvider<'_, P>
451
where
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
0
    fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> {
460
0
        self.0
461
0
            .load_any(key, req)?
462
0
            .downcast()
463
0
            .map_err(|e| e.with_req(key, req))
464
0
    }
465
}
466
467
#[cfg(test)]
468
mod 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
}