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