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