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 | //! Providers that filter resource requests. |
6 | //! |
7 | //! Requests that fail a filter test will return [`DataError`] of kind [`FilteredResource`]( |
8 | //! DataErrorKind::FilteredResource) and will not appear in [`IterableDynamicDataProvider`] iterators. |
9 | //! |
10 | //! The main struct is [`RequestFilterDataProvider`]. Although that struct can be created |
11 | //! directly, the traits in this module provide helper functions for common filtering patterns. |
12 | //! |
13 | //! To create a `RequestFilterDataProvider`, you can use the [`Filterable`] blanket function: |
14 | //! |
15 | //! ``` |
16 | //! use icu_provider_adapters::filter::Filterable; |
17 | //! |
18 | //! // now call .filterable() on any object to get a RequestFilterDataProvider |
19 | //! ``` |
20 | //! |
21 | //! # Examples |
22 | //! |
23 | //! ``` |
24 | //! use icu_locid::subtags::language; |
25 | //! use icu_provider::hello_world::*; |
26 | //! use icu_provider::prelude::*; |
27 | //! use icu_provider_adapters::filter::Filterable; |
28 | //! |
29 | //! // Only return German data from a HelloWorldProvider: |
30 | //! HelloWorldProvider |
31 | //! .filterable("Demo German-only filter" ) |
32 | //! .filter_by_langid(|langid| langid.language == language!("de" )); |
33 | //! ``` |
34 | //! |
35 | //! [`IterableDynamicDataProvider`]: icu_provider::datagen::IterableDynamicDataProvider |
36 | |
37 | mod impls; |
38 | |
39 | pub use impls::*; |
40 | |
41 | #[cfg (feature = "datagen" )] |
42 | use icu_provider::datagen; |
43 | use icu_provider::prelude::*; |
44 | |
45 | /// A data provider that selectively filters out data requests. |
46 | /// |
47 | /// Data requests that are rejected by the filter will return a [`DataError`] with kind |
48 | /// [`FilteredResource`](DataErrorKind::FilteredResource), and they will not be returned |
49 | /// by [`datagen::IterableDynamicDataProvider::supported_locales_for_key`]. |
50 | /// |
51 | /// Although this struct can be created directly, the traits in this module provide helper |
52 | /// functions for common filtering patterns. |
53 | #[allow (clippy::exhaustive_structs)] // this type is stable |
54 | #[derive (Debug)] |
55 | pub struct RequestFilterDataProvider<D, F> |
56 | where |
57 | F: Fn(DataRequest) -> bool, |
58 | { |
59 | /// The data provider to which we delegate requests. |
60 | pub inner: D, |
61 | |
62 | /// The predicate function. A return value of `true` indicates that the request should |
63 | /// proceed as normal; a return value of `false` will reject the request. |
64 | pub predicate: F, |
65 | |
66 | /// A name for this filter, used in error messages. |
67 | pub filter_name: &'static str, |
68 | } |
69 | |
70 | impl<D, F, M> DynamicDataProvider<M> for RequestFilterDataProvider<D, F> |
71 | where |
72 | F: Fn(DataRequest) -> bool, |
73 | M: DataMarker, |
74 | D: DynamicDataProvider<M>, |
75 | { |
76 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
77 | if (self.predicate)(req) { |
78 | self.inner.load_data(key, req) |
79 | } else { |
80 | Err(DataErrorKindDataError::FilteredResource |
81 | .with_str_context(self.filter_name) |
82 | .with_req(key, req)) |
83 | } |
84 | } |
85 | } |
86 | |
87 | impl<D, F, M> DataProvider<M> for RequestFilterDataProvider<D, F> |
88 | where |
89 | F: Fn(DataRequest) -> bool, |
90 | M: KeyedDataMarker, |
91 | D: DataProvider<M>, |
92 | { |
93 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
94 | if (self.predicate)(req) { |
95 | self.inner.load(req) |
96 | } else { |
97 | Err(DataErrorKindDataError::FilteredResource |
98 | .with_str_context(self.filter_name) |
99 | .with_req(M::KEY, req)) |
100 | } |
101 | } |
102 | } |
103 | |
104 | impl<D, F> BufferProvider for RequestFilterDataProvider<D, F> |
105 | where |
106 | F: Fn(DataRequest) -> bool, |
107 | D: BufferProvider, |
108 | { |
109 | fn load_buffer( |
110 | &self, |
111 | key: DataKey, |
112 | req: DataRequest, |
113 | ) -> Result<DataResponse<BufferMarker>, DataError> { |
114 | if (self.predicate)(req) { |
115 | self.inner.load_buffer(key, req) |
116 | } else { |
117 | Err(DataErrorKindDataError::FilteredResource |
118 | .with_str_context(self.filter_name) |
119 | .with_req(key, req)) |
120 | } |
121 | } |
122 | } |
123 | |
124 | impl<D, F> AnyProvider for RequestFilterDataProvider<D, F> |
125 | where |
126 | F: Fn(DataRequest) -> bool, |
127 | D: AnyProvider, |
128 | { |
129 | fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> { |
130 | if (self.predicate)(req) { |
131 | self.inner.load_any(key, req) |
132 | } else { |
133 | Err(DataErrorKindDataError::FilteredResource |
134 | .with_str_context(self.filter_name) |
135 | .with_req(key, req)) |
136 | } |
137 | } |
138 | } |
139 | |
140 | #[cfg (feature = "datagen" )] |
141 | impl<M, D, F> datagen::IterableDynamicDataProvider<M> for RequestFilterDataProvider<D, F> |
142 | where |
143 | M: DataMarker, |
144 | F: Fn(DataRequest) -> bool, |
145 | D: datagen::IterableDynamicDataProvider<M>, |
146 | { |
147 | fn supported_locales_for_key( |
148 | &self, |
149 | key: DataKey, |
150 | ) -> Result<alloc::vec::Vec<DataLocale>, DataError> { |
151 | self.inner.supported_locales_for_key(key).map(|vec| { |
152 | // Use filter_map instead of filter to avoid cloning the locale |
153 | vec.into_iter() |
154 | .filter_map(|locale| { |
155 | if (self.predicate)(DataRequest { |
156 | locale: &locale, |
157 | metadata: Default::default(), |
158 | }) { |
159 | Some(locale) |
160 | } else { |
161 | None |
162 | } |
163 | }) |
164 | .collect() |
165 | }) |
166 | } |
167 | } |
168 | |
169 | #[cfg (feature = "datagen" )] |
170 | impl<M, D, F> datagen::IterableDataProvider<M> for RequestFilterDataProvider<D, F> |
171 | where |
172 | M: KeyedDataMarker, |
173 | F: Fn(DataRequest) -> bool, |
174 | D: datagen::IterableDataProvider<M>, |
175 | { |
176 | fn supported_locales(&self) -> Result<alloc::vec::Vec<DataLocale>, DataError> { |
177 | self.inner.supported_locales().map(|vec| { |
178 | // Use filter_map instead of filter to avoid cloning the locale |
179 | vec.into_iter() |
180 | .filter_map(|locale| { |
181 | if (self.predicate)(DataRequest { |
182 | locale: &locale, |
183 | metadata: Default::default(), |
184 | }) { |
185 | Some(locale) |
186 | } else { |
187 | None |
188 | } |
189 | }) |
190 | .collect() |
191 | }) |
192 | } |
193 | } |
194 | |
195 | #[cfg (feature = "datagen" )] |
196 | impl<D, F, MFrom, MTo> datagen::DataConverter<MFrom, MTo> for RequestFilterDataProvider<D, F> |
197 | where |
198 | D: datagen::DataConverter<MFrom, MTo>, |
199 | MFrom: DataMarker, |
200 | MTo: DataMarker, |
201 | F: Fn(DataRequest) -> bool, |
202 | { |
203 | fn convert( |
204 | &self, |
205 | key: DataKey, |
206 | from: DataPayload<MFrom>, |
207 | ) -> Result<DataPayload<MTo>, (DataPayload<MFrom>, DataError)> { |
208 | // Conversions are type-agnostic |
209 | self.inner.convert(key, from) |
210 | } |
211 | } |
212 | |
213 | /// A blanket-implemented trait exposing the [`Self::filterable()`] function. |
214 | /// |
215 | /// For more details, see [`icu_provider_adapters::filter`](crate::filter). |
216 | pub trait Filterable: Sized { |
217 | /// Creates a filterable data provider with the given name for debugging. |
218 | /// |
219 | /// For more details, see [`icu_provider_adapters::filter`](crate::filter). |
220 | fn filterable( |
221 | self, |
222 | filter_name: &'static str, |
223 | ) -> RequestFilterDataProvider<Self, fn(DataRequest) -> bool>; |
224 | } |
225 | |
226 | impl<T> Filterable for T |
227 | where |
228 | T: Sized, |
229 | { |
230 | fn filterable( |
231 | self, |
232 | filter_name: &'static str, |
233 | ) -> RequestFilterDataProvider<Self, fn(DataRequest) -> bool> { |
234 | fn noop(_: DataRequest) -> bool { |
235 | true |
236 | } |
237 | RequestFilterDataProvider { |
238 | inner: self, |
239 | predicate: noop, |
240 | filter_name, |
241 | } |
242 | } |
243 | } |
244 | |