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::locale;
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: &locale!("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/// locale!("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 = 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 = 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::locale;
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: &locale!("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 AnyResponse| &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 DataResponse| &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 DataResponse| &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 DataResponse| &mut res.metadata,
296 )
297 }
298}
299