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