icu_provider_adapters/fallback/
mod.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
5//! A data provider wrapper that performs locale fallback.
6
7use crate::helpers::result_is_err_missing_locale;
8use icu_locid_transform::provider::*;
9use icu_provider::prelude::*;
10
11#[doc(hidden)] // moved
12pub use icu_locid_transform::fallback::{
13    LocaleFallbackIterator, LocaleFallbacker, LocaleFallbackerWithConfig,
14};
15#[doc(hidden)] // moved
16pub use icu_provider::fallback::LocaleFallbackConfig;
17
18/// A data provider wrapper that performs locale fallback. This enables arbitrary locales to be
19/// handled at runtime.
20///
21/// # Examples
22///
23/// ```
24/// use icu_locid::langid;
25/// use icu_provider::prelude::*;
26/// use icu_provider::hello_world::*;
27/// use icu_provider_adapters::fallback::LocaleFallbackProvider;
28///
29/// # let provider = icu_provider_blob::BlobDataProvider::try_new_from_static_blob(include_bytes!("../../tests/data/blob.postcard")).unwrap();
30/// # let provider = provider.as_deserializing();
31///
32/// let req = DataRequest {
33///     locale: &langid!("ja-JP").into(),
34///     metadata: Default::default(),
35/// };
36///
37/// // The provider does not have data for "ja-JP":
38/// DataProvider::<HelloWorldV1Marker>::load(&provider, req).expect_err("No fallback");
39///
40/// // But if we wrap the provider in a fallback provider...
41/// let provider = LocaleFallbackProvider::try_new_unstable(provider)
42///     .expect("Fallback data present");
43///
44/// // ...then we can load "ja-JP" based on "ja" data
45/// let response =
46///   DataProvider::<HelloWorldV1Marker>::load(&provider, req).expect("successful with vertical fallback");
47///
48/// assert_eq!(
49///     response.metadata.locale.unwrap(),
50///     langid!("ja").into(),
51/// );
52/// assert_eq!(
53///     response.payload.unwrap().get().message,
54///     "こんにちは世界",
55/// );
56/// ```
57#[derive(Clone, Debug)]
58pub struct LocaleFallbackProvider<P> {
59    inner: P,
60    fallbacker: LocaleFallbacker,
61}
62
63impl<P> LocaleFallbackProvider<P>
64where
65    P: DataProvider<LocaleFallbackLikelySubtagsV1Marker>
66        + DataProvider<LocaleFallbackParentsV1Marker>
67        + DataProvider<CollationFallbackSupplementV1Marker>,
68{
69    /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading
70    /// fallback data from it.
71    ///
72    /// If the data provider being wrapped does not contain fallback data, use
73    /// [`LocaleFallbackProvider::new_with_fallbacker`].
74    pub fn try_new_unstable(provider: P) -> Result<Self, DataError> {
75        let fallbacker = LocaleFallbacker::try_new_unstable(&provider)?;
76        Ok(Self {
77            inner: provider,
78            fallbacker,
79        })
80    }
81}
82
83impl<P> LocaleFallbackProvider<P>
84where
85    P: AnyProvider,
86{
87    /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading
88    /// fallback data from it.
89    ///
90    /// If the data provider being wrapped does not contain fallback data, use
91    /// [`LocaleFallbackProvider::new_with_fallbacker`].
92    pub fn try_new_with_any_provider(provider: P) -> Result<Self, DataError> {
93        let fallbacker = LocaleFallbacker::try_new_with_any_provider(&provider)?;
94        Ok(Self {
95            inner: provider,
96            fallbacker,
97        })
98    }
99}
100
101#[cfg(feature = "serde")]
102impl<P> LocaleFallbackProvider<P>
103where
104    P: BufferProvider,
105{
106    /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading
107    /// fallback data from it.
108    ///
109    /// If the data provider being wrapped does not contain fallback data, use
110    /// [`LocaleFallbackProvider::new_with_fallbacker`].
111    pub fn try_new_with_buffer_provider(provider: P) -> Result<Self, DataError> {
112        let fallbacker = LocaleFallbacker::try_new_with_buffer_provider(&provider)?;
113        Ok(Self {
114            inner: provider,
115            fallbacker,
116        })
117    }
118}
119
120impl<P> LocaleFallbackProvider<P> {
121    /// Wrap a provider with an arbitrary fallback engine.
122    ///
123    /// This relaxes the requirement that the wrapped provider contains its own fallback data.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use icu_locid::langid;
129    /// use icu_locid_transform::LocaleFallbacker;
130    /// use icu_provider::hello_world::*;
131    /// use icu_provider::prelude::*;
132    /// use icu_provider_adapters::fallback::LocaleFallbackProvider;
133    ///
134    /// let provider = HelloWorldProvider;
135    ///
136    /// let req = DataRequest {
137    ///     locale: &langid!("de-CH").into(),
138    ///     metadata: Default::default(),
139    /// };
140    ///
141    /// // There is no "de-CH" data in the `HelloWorldProvider`
142    /// DataProvider::<HelloWorldV1Marker>::load(&provider, req)
143    ///     .expect_err("No data for de-CH");
144    ///
145    /// // `HelloWorldProvider` does not contain fallback data,
146    /// // but we can construct a fallbacker with `icu_locid_transform`'s
147    /// // compiled data.
148    /// let provider = LocaleFallbackProvider::new_with_fallbacker(
149    ///     provider,
150    ///     LocaleFallbacker::new().static_to_owned(),
151    /// );
152    ///
153    /// // Now we can load the "de-CH" data via fallback to "de".
154    /// let german_hello_world: DataPayload<HelloWorldV1Marker> = provider
155    ///     .load(req)
156    ///     .expect("Loading should succeed")
157    ///     .take_payload()
158    ///     .expect("Data should be present");
159    ///
160    /// assert_eq!("Hallo Welt", german_hello_world.get().message);
161    /// ```
162    pub fn new_with_fallbacker(provider: P, fallbacker: LocaleFallbacker) -> Self {
163        Self {
164            inner: provider,
165            fallbacker,
166        }
167    }
168
169    /// Returns a reference to the inner provider, bypassing fallback.
170    pub fn inner(&self) -> &P {
171        &self.inner
172    }
173
174    /// Returns a mutable reference to the inner provider.
175    pub fn inner_mut(&mut self) -> &mut P {
176        &mut self.inner
177    }
178
179    /// Returns ownership of the inner provider to the caller.
180    pub fn into_inner(self) -> P {
181        self.inner
182    }
183
184    /// Run the fallback algorithm with the data request using the inner data provider.
185    /// Internal function; external clients should use one of the trait impls below.
186    ///
187    /// Function arguments:
188    ///
189    /// - F1 should perform a data load for a single DataRequest and return the result of it
190    /// - F2 should map from the provider-specific response type to DataResponseMetadata
191    fn run_fallback<F1, F2, R>(
192        &self,
193        key: DataKey,
194        mut base_req: DataRequest,
195        mut f1: F1,
196        mut f2: F2,
197    ) -> Result<R, DataError>
198    where
199        F1: FnMut(DataRequest) -> Result<R, DataError>,
200        F2: FnMut(&mut R) -> &mut DataResponseMetadata,
201    {
202        if key.metadata().singleton {
203            return f1(base_req);
204        }
205        let mut fallback_iterator = self
206            .fallbacker
207            .for_config(key.fallback_config())
208            .fallback_for(base_req.locale.clone());
209        let base_silent = core::mem::replace(&mut base_req.metadata.silent, true);
210        loop {
211            let result = f1(DataRequest {
212                locale: fallback_iterator.get(),
213                metadata: base_req.metadata,
214            });
215            if !result_is_err_missing_locale(&result) {
216                return result
217                    .map(|mut res| {
218                        f2(&mut res).locale = Some(fallback_iterator.take());
219                        res
220                    })
221                    // Log the original request rather than the fallback request
222                    .map_err(|e| {
223                        base_req.metadata.silent = base_silent;
224                        e.with_req(key, base_req)
225                    });
226            }
227            // If we just checked und, break out of the loop.
228            if fallback_iterator.get().is_und() {
229                break;
230            }
231            fallback_iterator.step();
232        }
233        base_req.metadata.silent = base_silent;
234        Err(DataErrorKind::MissingLocale.with_req(key, base_req))
235    }
236}
237
238impl<P> AnyProvider for LocaleFallbackProvider<P>
239where
240    P: AnyProvider,
241{
242    fn load_any(&self, key: DataKey, base_req: DataRequest) -> Result<AnyResponse, DataError> {
243        self.run_fallback(
244            key,
245            base_req,
246            |req| self.inner.load_any(key, req),
247            |res| &mut res.metadata,
248        )
249    }
250}
251
252impl<P> BufferProvider for LocaleFallbackProvider<P>
253where
254    P: BufferProvider,
255{
256    fn load_buffer(
257        &self,
258        key: DataKey,
259        base_req: DataRequest,
260    ) -> Result<DataResponse<BufferMarker>, DataError> {
261        self.run_fallback(
262            key,
263            base_req,
264            |req| self.inner.load_buffer(key, req),
265            |res| &mut res.metadata,
266        )
267    }
268}
269
270impl<P, M> DynamicDataProvider<M> for LocaleFallbackProvider<P>
271where
272    P: DynamicDataProvider<M>,
273    M: DataMarker,
274{
275    fn load_data(&self, key: DataKey, base_req: DataRequest) -> Result<DataResponse<M>, DataError> {
276        self.run_fallback(
277            key,
278            base_req,
279            |req| self.inner.load_data(key, req),
280            |res| &mut res.metadata,
281        )
282    }
283}
284
285impl<P, M> DataProvider<M> for LocaleFallbackProvider<P>
286where
287    P: DataProvider<M>,
288    M: KeyedDataMarker,
289{
290    fn load(&self, base_req: DataRequest) -> Result<DataResponse<M>, DataError> {
291        self.run_fallback(
292            M::KEY,
293            base_req,
294            |req| self.inner.load(req),
295            |res| &mut res.metadata,
296        )
297    }
298}