icu_provider_adapters/filter/
impls.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 super::*;
6use alloc::boxed::Box;
7use icu_provider::prelude::*;
8
9use icu_locid::LanguageIdentifier;
10
11type RequestFilterDataProviderOutput<'a, D> =
12    RequestFilterDataProvider<D, Box<dyn Fn(DataRequest) -> bool + Sync + 'a>>;
13
14impl<D, F> RequestFilterDataProvider<D, F>
15where
16    F: Fn(DataRequest) -> bool + Sync,
17{
18    /// Filter out data requests with certain langids according to the predicate function. The
19    /// predicate should return `true` to allow a langid and `false` to reject a langid.
20    ///
21    /// Data requests with no langid will be allowed. To reject data requests without a langid,
22    /// chain this with [`Self::require_langid`].
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// use icu_locid::LanguageIdentifier;
28    /// use icu_locid::{langid, subtags::language};
29    /// use icu_provider::datagen::*;
30    /// use icu_provider::hello_world::*;
31    /// use icu_provider::prelude::*;
32    /// use icu_provider_adapters::filter::Filterable;
33    ///
34    /// let provider = HelloWorldProvider
35    ///     .filterable("Demo no-English filter")
36    ///     .filter_by_langid(|langid| langid.language != language!("en"));
37    ///
38    /// // German requests should succeed:
39    /// let req_de = DataRequest {
40    ///     locale: &langid!("de").into(),
41    ///     metadata: Default::default(),
42    /// };
43    /// let response: Result<DataResponse<HelloWorldV1Marker>, _> =
44    ///     provider.load(req_de);
45    /// assert!(matches!(response, Ok(_)));
46    ///
47    /// // English requests should fail:
48    /// let req_en = DataRequest {
49    ///     locale: &langid!("en-US").into(),
50    ///     metadata: Default::default(),
51    /// };
52    /// let response: Result<DataResponse<HelloWorldV1Marker>, _> =
53    ///     provider.load(req_en);
54    /// assert!(matches!(
55    ///     response,
56    ///     Err(DataError {
57    ///         kind: DataErrorKind::FilteredResource,
58    ///         ..
59    ///     })
60    /// ));
61    ///
62    /// // English should not appear in the iterator result:
63    /// let supported_langids = provider
64    ///     .supported_locales()
65    ///     .expect("Should successfully make an iterator of supported locales")
66    ///     .into_iter()
67    ///     .map(|options| options.get_langid())
68    ///     .collect::<Vec<LanguageIdentifier>>();
69    /// assert!(supported_langids.contains(&langid!("de")));
70    /// assert!(!supported_langids.contains(&langid!("en")));
71    /// ```
72    pub fn filter_by_langid<'a>(
73        self,
74        predicate: impl Fn(&LanguageIdentifier) -> bool + Sync + 'a,
75    ) -> RequestFilterDataProviderOutput<'a, D>
76    where
77        F: 'a,
78    {
79        let old_predicate = self.predicate;
80        RequestFilterDataProvider {
81            inner: self.inner,
82            predicate: Box::new(move |request| -> bool {
83                if !(old_predicate)(request) {
84                    return false;
85                }
86                predicate(&request.locale.get_langid())
87            }),
88            filter_name: self.filter_name,
89        }
90    }
91
92    /// Filter out data request except those having a language identifier that exactly matches
93    /// one in the allowlist.
94    ///
95    /// This will be replaced with a smarter algorithm for locale filtering; see
96    /// <https://github.com/unicode-org/icu4x/issues/834>
97    ///
98    /// Data requests with no langid will be allowed. To reject data requests without a langid,
99    /// chain this with [`Self::require_langid`].
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use icu_locid::langid;
105    /// use icu_provider::hello_world::*;
106    /// use icu_provider::prelude::*;
107    /// use icu_provider_adapters::filter::Filterable;
108    ///
109    /// let allowlist = [langid!("de"), langid!("zh")];
110    /// let provider = HelloWorldProvider
111    ///     .filterable("Demo German+Chinese filter")
112    ///     .filter_by_langid_allowlist_strict(&allowlist);
113    ///
114    /// // German requests should succeed:
115    /// let req_de = DataRequest {
116    ///     locale: &langid!("de").into(),
117    ///     metadata: Default::default(),
118    /// };
119    /// let response: Result<DataResponse<HelloWorldV1Marker>, _> =
120    ///     provider.load(req_de);
121    /// assert!(matches!(response, Ok(_)));
122    ///
123    /// // English requests should fail:
124    /// let req_en = DataRequest {
125    ///     locale: &langid!("en-US").into(),
126    ///     metadata: Default::default(),
127    /// };
128    /// let response: Result<DataResponse<HelloWorldV1Marker>, _> =
129    ///     provider.load(req_en);
130    /// assert!(matches!(
131    ///     response,
132    ///     Err(DataError {
133    ///         kind: DataErrorKind::FilteredResource,
134    ///         ..
135    ///     })
136    /// ));
137    /// assert_eq!(
138    ///     response.unwrap_err().str_context,
139    ///     Some("Demo German+Chinese filter")
140    /// );
141    /// ```
142    pub fn filter_by_langid_allowlist_strict<'a>(
143        self,
144        allowlist: &'a [LanguageIdentifier],
145    ) -> RequestFilterDataProviderOutput<'a, D>
146    where
147        F: 'a,
148    {
149        let old_predicate = self.predicate;
150        RequestFilterDataProvider {
151            inner: self.inner,
152            predicate: Box::new(move |request| -> bool {
153                if !(old_predicate)(request) {
154                    return false;
155                }
156                request.locale.is_langid_und() || allowlist.contains(&request.locale.get_langid())
157            }),
158            filter_name: self.filter_name,
159        }
160    }
161
162    /// Require that data requests contain a langid.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use icu_locid::langid;
168    /// use icu_provider::hello_world::*;
169    /// use icu_provider::prelude::*;
170    /// use icu_provider_adapters::filter::Filterable;
171    ///
172    /// let provider = HelloWorldProvider
173    ///     .filterable("Demo require-langid filter")
174    ///     .require_langid();
175    ///
176    /// // Requests with a langid should succeed:
177    /// let req_with_langid = DataRequest {
178    ///     locale: &langid!("de").into(),
179    ///     metadata: Default::default(),
180    /// };
181    /// let response: Result<DataResponse<HelloWorldV1Marker>, _> =
182    ///     provider.load(req_with_langid);
183    /// assert!(matches!(response, Ok(_)));
184    ///
185    /// // Requests without a langid should fail:
186    /// let req_no_langid = DataRequest {
187    ///     locale: Default::default(),
188    ///     metadata: Default::default(),
189    /// };
190    /// let response: Result<DataResponse<HelloWorldV1Marker>, _> =
191    ///     provider.load(req_no_langid);
192    /// assert!(matches!(
193    ///     response,
194    ///     Err(DataError {
195    ///         kind: DataErrorKind::FilteredResource,
196    ///         ..
197    ///     })
198    /// ));
199    /// ```
200    pub fn require_langid<'a>(self) -> RequestFilterDataProviderOutput<'a, D>
201    where
202        F: 'a,
203    {
204        let old_predicate = self.predicate;
205        RequestFilterDataProvider {
206            inner: self.inner,
207            predicate: Box::new(move |request| -> bool {
208                if !(old_predicate)(request) {
209                    return false;
210                }
211                !request.locale.is_langid_und()
212            }),
213            filter_name: self.filter_name,
214        }
215    }
216}