Skip to main content

icu_provider/
data_provider.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
5use core::marker::PhantomData;
6use yoke::Yokeable;
7
8#[cfg(feature = "alloc")]
9use alloc::boxed::Box;
10
11use crate::prelude::*;
12
13/// A data provider that loads data for a specific [`DataMarkerInfo`].
14pub trait DataProvider<M>
15where
16    M: DataMarker,
17{
18    /// Query the provider for data, returning the result.
19    ///
20    /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an
21    /// Error with more information.
22    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError>;
23}
24
25impl<M, P> DataProvider<M> for &P
26where
27    M: DataMarker,
28    P: DataProvider<M> + ?Sized,
29{
30    #[inline]
31    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
32        (*self).load(req)
33    }
34}
35
36#[cfg(feature = "alloc")]
37impl<M, P> DataProvider<M> for Box<P>
38where
39    M: DataMarker,
40    P: DataProvider<M> + ?Sized,
41{
42    #[inline]
43    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
44        (**self).load(req)
45    }
46}
47
48#[cfg(feature = "alloc")]
49impl<M, P> DataProvider<M> for alloc::rc::Rc<P>
50where
51    M: DataMarker,
52    P: DataProvider<M> + ?Sized,
53{
54    #[inline]
55    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
56        (**self).load(req)
57    }
58}
59
60#[cfg(target_has_atomic = "ptr")]
61#[cfg(feature = "alloc")]
62impl<M, P> DataProvider<M> for alloc::sync::Arc<P>
63where
64    M: DataMarker,
65    P: DataProvider<M> + ?Sized,
66{
67    #[inline]
68    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
69        (**self).load(req)
70    }
71}
72
73/// A data provider that can determine whether it can load a particular data identifier,
74/// potentially cheaper than actually performing the load.
75pub trait DryDataProvider<M: DataMarker>: DataProvider<M> {
76    /// This method goes through the motions of [`load`], but only returns the metadata.
77    ///
78    /// If `dry_load` returns an error, [`load`] must return the same error, but
79    /// not vice-versa. Concretely, [`load`] could return deserialization or I/O errors
80    /// that `dry_load` cannot predict.
81    ///
82    /// [`load`]: DataProvider::load
83    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError>;
84}
85
86impl<M, P> DryDataProvider<M> for &P
87where
88    M: DataMarker,
89    P: DryDataProvider<M> + ?Sized,
90{
91    #[inline]
92    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError> {
93        (*self).dry_load(req)
94    }
95}
96
97#[cfg(feature = "alloc")]
98impl<M, P> DryDataProvider<M> for Box<P>
99where
100    M: DataMarker,
101    P: DryDataProvider<M> + ?Sized,
102{
103    #[inline]
104    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError> {
105        (**self).dry_load(req)
106    }
107}
108
109#[cfg(feature = "alloc")]
110impl<M, P> DryDataProvider<M> for alloc::rc::Rc<P>
111where
112    M: DataMarker,
113    P: DryDataProvider<M> + ?Sized,
114{
115    #[inline]
116    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError> {
117        (**self).dry_load(req)
118    }
119}
120
121#[cfg(target_has_atomic = "ptr")]
122#[cfg(feature = "alloc")]
123impl<M, P> DryDataProvider<M> for alloc::sync::Arc<P>
124where
125    M: DataMarker,
126    P: DryDataProvider<M> + ?Sized,
127{
128    #[inline]
129    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError> {
130        (**self).dry_load(req)
131    }
132}
133
134/// A [`DataProvider`] that can iterate over all supported [`DataIdentifierCow`]s.
135///
136/// The provider is not allowed to return `Ok` for requests that were not returned by `iter_ids`,
137/// and must not fail with a [`DataErrorKind::IdentifierNotFound`] for requests that were returned.
138///
139/// ✨ *Enabled with the `alloc` Cargo feature.*
140#[cfg(feature = "alloc")]
141pub trait IterableDataProvider<M: DataMarker>: DataProvider<M> {
142    /// Returns a set of [`DataIdentifierCow`].
143    fn iter_ids(&self) -> Result<alloc::collections::BTreeSet<DataIdentifierCow<'_>>, DataError>;
144}
145
146/// A data provider that loads data for a specific data type.
147///
148/// Unlike [`DataProvider`], there may be multiple markers corresponding to the same data type.
149pub trait DynamicDataProvider<M>
150where
151    M: DynamicDataMarker,
152{
153    /// Query the provider for data, returning the result.
154    ///
155    /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an
156    /// Error with more information.
157    fn load_data(
158        &self,
159        marker: DataMarkerInfo,
160        req: DataRequest,
161    ) -> Result<DataResponse<M>, DataError>;
162}
163
164impl<M, P> DynamicDataProvider<M> for &P
165where
166    M: DynamicDataMarker,
167    P: DynamicDataProvider<M> + ?Sized,
168{
169    #[inline]
170    fn load_data(
171        &self,
172        marker: DataMarkerInfo,
173        req: DataRequest,
174    ) -> Result<DataResponse<M>, DataError> {
175        (*self).load_data(marker, req)
176    }
177}
178
179#[cfg(feature = "alloc")]
180impl<M, P> DynamicDataProvider<M> for Box<P>
181where
182    M: DynamicDataMarker,
183    P: DynamicDataProvider<M> + ?Sized,
184{
185    #[inline]
186    fn load_data(
187        &self,
188        marker: DataMarkerInfo,
189        req: DataRequest,
190    ) -> Result<DataResponse<M>, DataError> {
191        (**self).load_data(marker, req)
192    }
193}
194
195#[cfg(feature = "alloc")]
196impl<M, P> DynamicDataProvider<M> for alloc::rc::Rc<P>
197where
198    M: DynamicDataMarker,
199    P: DynamicDataProvider<M> + ?Sized,
200{
201    #[inline]
202    fn load_data(
203        &self,
204        marker: DataMarkerInfo,
205        req: DataRequest,
206    ) -> Result<DataResponse<M>, DataError> {
207        (**self).load_data(marker, req)
208    }
209}
210
211#[cfg(target_has_atomic = "ptr")]
212#[cfg(feature = "alloc")]
213impl<M, P> DynamicDataProvider<M> for alloc::sync::Arc<P>
214where
215    M: DynamicDataMarker,
216    P: DynamicDataProvider<M> + ?Sized,
217{
218    #[inline]
219    fn load_data(
220        &self,
221        marker: DataMarkerInfo,
222        req: DataRequest,
223    ) -> Result<DataResponse<M>, DataError> {
224        (**self).load_data(marker, req)
225    }
226}
227
228/// A dynanmic data provider that can determine whether it can load a particular data identifier,
229/// potentially cheaper than actually performing the load.
230pub trait DynamicDryDataProvider<M: DynamicDataMarker>: DynamicDataProvider<M> {
231    /// This method goes through the motions of [`load_data`], but only returns the metadata.
232    ///
233    /// If `dry_load_data` returns an error, [`load_data`] must return the same error, but
234    /// not vice-versa. Concretely, [`load_data`] could return deserialization or I/O errors
235    /// that `dry_load_data` cannot predict.
236    ///
237    /// [`load_data`]: DynamicDataProvider::load_data
238    fn dry_load_data(
239        &self,
240        marker: DataMarkerInfo,
241        req: DataRequest,
242    ) -> Result<DataResponseMetadata, DataError>;
243}
244
245impl<M, P> DynamicDryDataProvider<M> for &P
246where
247    M: DynamicDataMarker,
248    P: DynamicDryDataProvider<M> + ?Sized,
249{
250    #[inline]
251    fn dry_load_data(
252        &self,
253        marker: DataMarkerInfo,
254        req: DataRequest,
255    ) -> Result<DataResponseMetadata, DataError> {
256        (*self).dry_load_data(marker, req)
257    }
258}
259
260#[cfg(feature = "alloc")]
261impl<M, P> DynamicDryDataProvider<M> for Box<P>
262where
263    M: DynamicDataMarker,
264    P: DynamicDryDataProvider<M> + ?Sized,
265{
266    #[inline]
267    fn dry_load_data(
268        &self,
269        marker: DataMarkerInfo,
270        req: DataRequest,
271    ) -> Result<DataResponseMetadata, DataError> {
272        (**self).dry_load_data(marker, req)
273    }
274}
275
276#[cfg(feature = "alloc")]
277impl<M, P> DynamicDryDataProvider<M> for alloc::rc::Rc<P>
278where
279    M: DynamicDataMarker,
280    P: DynamicDryDataProvider<M> + ?Sized,
281{
282    #[inline]
283    fn dry_load_data(
284        &self,
285        marker: DataMarkerInfo,
286        req: DataRequest,
287    ) -> Result<DataResponseMetadata, DataError> {
288        (**self).dry_load_data(marker, req)
289    }
290}
291
292#[cfg(target_has_atomic = "ptr")]
293#[cfg(feature = "alloc")]
294impl<M, P> DynamicDryDataProvider<M> for alloc::sync::Arc<P>
295where
296    M: DynamicDataMarker,
297    P: DynamicDryDataProvider<M> + ?Sized,
298{
299    #[inline]
300    fn dry_load_data(
301        &self,
302        marker: DataMarkerInfo,
303        req: DataRequest,
304    ) -> Result<DataResponseMetadata, DataError> {
305        (**self).dry_load_data(marker, req)
306    }
307}
308
309/// A [`DynamicDataProvider`] that can iterate over all supported [`DataIdentifierCow`]s for a certain marker.
310///
311/// The provider is not allowed to return `Ok` for requests that were not returned by `iter_ids`,
312/// and must not fail with a [`DataErrorKind::IdentifierNotFound`] for requests that were returned.
313///
314/// ✨ *Enabled with the `alloc` Cargo feature.*
315#[cfg(feature = "alloc")]
316pub trait IterableDynamicDataProvider<M: DynamicDataMarker>: DynamicDataProvider<M> {
317    /// Given a [`DataMarkerInfo`], returns a set of [`DataIdentifierCow`].
318    fn iter_ids_for_marker(
319        &self,
320        marker: DataMarkerInfo,
321    ) -> Result<alloc::collections::BTreeSet<DataIdentifierCow<'_>>, DataError>;
322}
323
324#[cfg(feature = "alloc")]
325impl<M, P> IterableDynamicDataProvider<M> for Box<P>
326where
327    M: DynamicDataMarker,
328    P: IterableDynamicDataProvider<M> + ?Sized,
329{
330    fn iter_ids_for_marker(
331        &self,
332        marker: DataMarkerInfo,
333    ) -> Result<alloc::collections::BTreeSet<DataIdentifierCow<'_>>, DataError> {
334        (**self).iter_ids_for_marker(marker)
335    }
336}
337
338/// A data provider that loads data for a specific data type.
339///
340/// Unlike [`DataProvider`], the provider is bound to a specific marker ahead of time.
341///
342/// This crate provides [`DataProviderWithMarker`] which implements this trait on a single provider
343/// with a single marker. However, this trait can also be implemented on providers that fork between
344/// multiple markers that all return the same data type. For example, it can abstract over many
345/// calendar systems in the datetime formatter.
346pub trait BoundDataProvider<M>
347where
348    M: DynamicDataMarker,
349{
350    /// Query the provider for data, returning the result.
351    ///
352    /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an
353    /// Error with more information.
354    fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError>;
355    /// Returns the [`DataMarkerInfo`] that this provider uses for loading data.
356    fn bound_marker(&self) -> DataMarkerInfo;
357}
358
359impl<M, P> BoundDataProvider<M> for &P
360where
361    M: DynamicDataMarker,
362    P: BoundDataProvider<M> + ?Sized,
363{
364    #[inline]
365    fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
366        (*self).load_bound(req)
367    }
368    #[inline]
369    fn bound_marker(&self) -> DataMarkerInfo {
370        (*self).bound_marker()
371    }
372}
373
374#[cfg(feature = "alloc")]
375impl<M, P> BoundDataProvider<M> for Box<P>
376where
377    M: DynamicDataMarker,
378    P: BoundDataProvider<M> + ?Sized,
379{
380    #[inline]
381    fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
382        (**self).load_bound(req)
383    }
384    #[inline]
385    fn bound_marker(&self) -> DataMarkerInfo {
386        (**self).bound_marker()
387    }
388}
389
390#[cfg(feature = "alloc")]
391impl<M, P> BoundDataProvider<M> for alloc::rc::Rc<P>
392where
393    M: DynamicDataMarker,
394    P: BoundDataProvider<M> + ?Sized,
395{
396    #[inline]
397    fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
398        (**self).load_bound(req)
399    }
400    #[inline]
401    fn bound_marker(&self) -> DataMarkerInfo {
402        (**self).bound_marker()
403    }
404}
405
406#[cfg(target_has_atomic = "ptr")]
407#[cfg(feature = "alloc")]
408impl<M, P> BoundDataProvider<M> for alloc::sync::Arc<P>
409where
410    M: DynamicDataMarker,
411    P: BoundDataProvider<M> + ?Sized,
412{
413    #[inline]
414    fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
415        (**self).load_bound(req)
416    }
417    #[inline]
418    fn bound_marker(&self) -> DataMarkerInfo {
419        (**self).bound_marker()
420    }
421}
422
423/// A [`DataProvider`] associated with a specific marker.
424///
425/// Implements [`BoundDataProvider`].
426#[derive(#[automatically_derived]
impl<M: ::core::fmt::Debug, P: ::core::fmt::Debug> ::core::fmt::Debug for
    DataProviderWithMarker<M, P> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "DataProviderWithMarker", "inner", &self.inner, "_marker",
            &&self._marker)
    }
}Debug)]
427pub struct DataProviderWithMarker<M, P> {
428    inner: P,
429    _marker: PhantomData<M>,
430}
431
432impl<M, P> DataProviderWithMarker<M, P>
433where
434    M: DataMarker,
435    P: DataProvider<M>,
436{
437    /// Creates a [`DataProviderWithMarker`] from a [`DataProvider`] with a [`DataMarker`].
438    pub const fn new(inner: P) -> Self {
439        Self {
440            inner,
441            _marker: PhantomData,
442        }
443    }
444}
445
446impl<M, M0, Y, P> BoundDataProvider<M0> for DataProviderWithMarker<M, P>
447where
448    M: DataMarker<DataStruct = Y>,
449    M0: DynamicDataMarker<DataStruct = Y>,
450    Y: for<'a> Yokeable<'a>,
451    P: DataProvider<M>,
452{
453    #[inline]
454    fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M0>, DataError> {
455        self.inner.load(req).map(DataResponse::cast)
456    }
457    #[inline]
458    fn bound_marker(&self) -> DataMarkerInfo {
459        M::INFO
460    }
461}
462
463#[cfg(test)]
464mod test {
465
466    use super::*;
467    use crate::hello_world::*;
468    use alloc::borrow::Cow;
469    use alloc::string::String;
470    use core::fmt::Debug;
471    use serde::{Deserialize, Serialize};
472
473    // This tests DataProvider borrow semantics with a dummy data provider based on a
474    // JSON string. It also exercises most of the data provider code paths.
475
476    /// A data struct serialization-compatible with [`HelloWorld`] used for testing mismatched types
477    #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, yoke::Yokeable)]
478    pub struct HelloAlt {
479        message: String,
480    }
481
482    data_marker!(HelloAltMarkerV1, HelloAlt);
483
484    #[derive(Deserialize, Debug, Clone, Default, PartialEq)]
485    struct HelloCombined<'data> {
486        #[serde(borrow)]
487        pub hello_v1: HelloWorld<'data>,
488        pub hello_alt: HelloAlt,
489    }
490
491    /// A [`DataProvider`] that owns its data, returning an Rc-variant [`DataPayload`].
492    /// Supports only [`HelloWorldV1`]. Uses `impl_dynamic_data_provider!()`.
493    #[derive(Debug)]
494    struct DataWarehouse {
495        hello_v1: HelloWorld<'static>,
496        hello_alt: HelloAlt,
497    }
498
499    impl DataProvider<HelloWorldV1> for DataWarehouse {
500        fn load(&self, _: DataRequest) -> Result<DataResponse<HelloWorldV1>, DataError> {
501            Ok(DataResponse {
502                metadata: DataResponseMetadata::default(),
503                payload: DataPayload::from_owned(self.hello_v1.clone()),
504            })
505        }
506    }
507
508    /// A [`DataProvider`] that supports both [`HelloWorldV1`] and [`HelloAltV1`].
509    #[derive(Debug)]
510    struct DataProvider2 {
511        data: DataWarehouse,
512    }
513
514    impl From<DataWarehouse> for DataProvider2 {
515        fn from(warehouse: DataWarehouse) -> Self {
516            DataProvider2 { data: warehouse }
517        }
518    }
519
520    impl DataProvider<HelloWorldV1> for DataProvider2 {
521        fn load(&self, _: DataRequest) -> Result<DataResponse<HelloWorldV1>, DataError> {
522            Ok(DataResponse {
523                metadata: DataResponseMetadata::default(),
524                payload: DataPayload::from_owned(self.data.hello_v1.clone()),
525            })
526        }
527    }
528
529    impl DataProvider<HelloAltMarkerV1> for DataProvider2 {
530        fn load(&self, _: DataRequest) -> Result<DataResponse<HelloAltMarkerV1>, DataError> {
531            Ok(DataResponse {
532                metadata: DataResponseMetadata::default(),
533                payload: DataPayload::from_owned(self.data.hello_alt.clone()),
534            })
535        }
536    }
537
538    const DATA: &str = r#"{
539        "hello_v1": {
540            "message": "Hello "
541        },
542        "hello_alt": {
543            "message": "Hello Alt"
544        }
545    }"#;
546
547    fn get_warehouse(data: &'static str) -> DataWarehouse {
548        let data: HelloCombined = serde_json::from_str(data).expect("Well-formed data");
549        DataWarehouse {
550            hello_v1: data.hello_v1,
551            hello_alt: data.hello_alt,
552        }
553    }
554
555    fn get_payload_v1<P: DataProvider<HelloWorldV1> + ?Sized>(
556        provider: &P,
557    ) -> Result<DataPayload<HelloWorldV1>, DataError> {
558        provider.load(Default::default()).map(|r| r.payload)
559    }
560
561    fn get_payload_alt<P: DataProvider<HelloAltMarkerV1> + ?Sized>(
562        provider: &P,
563    ) -> Result<DataPayload<HelloAltMarkerV1>, DataError> {
564        provider.load(Default::default()).map(|r| r.payload)
565    }
566
567    #[test]
568    fn test_warehouse_owned() {
569        let warehouse = get_warehouse(DATA);
570        let hello_data = get_payload_v1(&warehouse).unwrap();
571        assert!(matches!(
572            hello_data.get(),
573            HelloWorld {
574                message: Cow::Borrowed(_),
575            }
576        ));
577    }
578
579    #[test]
580    fn test_warehouse_owned_dyn_generic() {
581        let warehouse = get_warehouse(DATA);
582        let hello_data = get_payload_v1(&warehouse as &dyn DataProvider<HelloWorldV1>).unwrap();
583        assert!(matches!(
584            hello_data.get(),
585            HelloWorld {
586                message: Cow::Borrowed(_),
587            }
588        ));
589    }
590
591    #[test]
592    fn test_provider2() {
593        let warehouse = get_warehouse(DATA);
594        let provider = DataProvider2::from(warehouse);
595        let hello_data = get_payload_v1(&provider).unwrap();
596        assert!(matches!(
597            hello_data.get(),
598            HelloWorld {
599                message: Cow::Borrowed(_),
600            }
601        ));
602    }
603
604    #[test]
605    fn test_provider2_dyn_generic() {
606        let warehouse = get_warehouse(DATA);
607        let provider = DataProvider2::from(warehouse);
608        let hello_data = get_payload_v1(&provider as &dyn DataProvider<HelloWorldV1>).unwrap();
609        assert!(matches!(
610            hello_data.get(),
611            HelloWorld {
612                message: Cow::Borrowed(_),
613            }
614        ));
615    }
616
617    #[test]
618    fn test_provider2_dyn_generic_alt() {
619        let warehouse = get_warehouse(DATA);
620        let provider = DataProvider2::from(warehouse);
621        let hello_data = get_payload_alt(&provider as &dyn DataProvider<HelloAltMarkerV1>).unwrap();
622        assert!(matches!(hello_data.get(), HelloAlt { .. }));
623    }
624
625    fn check_v1_v2<P>(d: &P)
626    where
627        P: DataProvider<HelloWorldV1> + DataProvider<HelloAltMarkerV1> + ?Sized,
628    {
629        let v1: DataPayload<HelloWorldV1> = d.load(Default::default()).unwrap().payload;
630        let v2: DataPayload<HelloAltMarkerV1> = d.load(Default::default()).unwrap().payload;
631        if v1.get().message == v2.get().message {
632            panic!()
633        }
634    }
635
636    #[test]
637    fn test_v1_v2_generic() {
638        let warehouse = get_warehouse(DATA);
639        let provider = DataProvider2::from(warehouse);
640        check_v1_v2(&provider);
641    }
642}