icu_provider_adapters/filter/
mod.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
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
39#[cfg(feature = "datagen")]
40use icu_provider::datagen;
41use 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)]
53pub struct RequestFilterDataProvider<D, F>
54where
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
68impl<D, F, M> DynamicDataProvider<M> for RequestFilterDataProvider<D, F>
69where
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(DataErrorKind::FilteredResource
79                .with_str_context(self.filter_name)
80                .with_req(key, req))
81        }
82    }
83}
84
85impl<D, F, M> DataProvider<M> for RequestFilterDataProvider<D, F>
86where
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(DataErrorKind::FilteredResource
96                .with_str_context(self.filter_name)
97                .with_req(M::KEY, req))
98        }
99    }
100}
101
102impl<D, F> BufferProvider for RequestFilterDataProvider<D, F>
103where
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(DataErrorKind::FilteredResource
116                .with_str_context(self.filter_name)
117                .with_req(key, req))
118        }
119    }
120}
121
122impl<D, F> AnyProvider for RequestFilterDataProvider<D, F>
123where
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(DataErrorKind::FilteredResource
132                .with_str_context(self.filter_name)
133                .with_req(key, req))
134        }
135    }
136}
137
138#[cfg(feature = "datagen")]
139impl<M, D, F> datagen::IterableDynamicDataProvider<M> for RequestFilterDataProvider<D, F>
140where
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")]
168impl<M, D, F> datagen::IterableDataProvider<M> for RequestFilterDataProvider<D, F>
169where
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")]
194impl<D, F, MFrom, MTo> datagen::DataConverter<MFrom, MTo> for RequestFilterDataProvider<D, F>
195where
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).
214pub 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
224impl<T> Filterable for T
225where
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}