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
37mod impls;
38
39pub use impls::*;
40
41#[cfg(feature = "datagen")]
42use icu_provider::datagen;
43use 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)]
55pub struct RequestFilterDataProvider<D, F>
56where
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
70impl<D, F, M> DynamicDataProvider<M> for RequestFilterDataProvider<D, F>
71where
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
87impl<D, F, M> DataProvider<M> for RequestFilterDataProvider<D, F>
88where
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
104impl<D, F> BufferProvider for RequestFilterDataProvider<D, F>
105where
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
124impl<D, F> AnyProvider for RequestFilterDataProvider<D, F>
125where
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")]
141impl<M, D, F> datagen::IterableDynamicDataProvider<M> for RequestFilterDataProvider<D, F>
142where
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")]
170impl<M, D, F> datagen::IterableDataProvider<M> for RequestFilterDataProvider<D, F>
171where
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")]
196impl<D, F, MFrom, MTo> datagen::DataConverter<MFrom, MTo> for RequestFilterDataProvider<D, F>
197where
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).
216pub 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
226impl<T> Filterable for T
227where
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