Skip to main content

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 [`Filtered`](
8//! DataErrorKind::IdentifierNotFound) and will not appear in [`IterableDynamicDataProvider`] iterators.
9//!
10//! # Examples
11//!
12//! ```
13//! use icu_locale::subtags::language;
14//! use icu_provider::hello_world::*;
15//! use icu_provider::prelude::*;
16//! use icu_provider_adapters::filter::FilterDataProvider;
17//!
18//! // Only return German data from a HelloWorldProvider:
19//! FilterDataProvider::new(HelloWorldProvider, "Demo German-only filter")
20//!     .with_filter(|id| id.locale.language == language!("de"));
21//! ```
22
23mod impls;
24
25use alloc::collections::BTreeSet;
26#[cfg(feature = "export")]
27use icu_provider::export::ExportableProvider;
28use icu_provider::prelude::*;
29
30/// A data provider that selectively filters out data requests.
31///
32/// Data requests that are rejected by the filter will return a [`DataError`] with kind
33/// [`Filtered`](DataErrorKind::IdentifierNotFound), and they will not be returned
34/// by [`IterableDynamicDataProvider::iter_ids_for_marker`].
35///
36/// Although this struct can be created directly, the traits in this module provide helper
37/// functions for common filtering patterns.
38#[allow(clippy::exhaustive_structs)] // this type is stable
39#[derive(Debug)]
40pub struct FilterDataProvider<D, F>
41where
42    F: Fn(DataIdentifierBorrowed) -> bool,
43{
44    /// The data provider to which we delegate requests.
45    pub inner: D,
46
47    /// The predicate function. A return value of `true` indicates that the request should
48    /// proceed as normal; a return value of `false` will reject the request.
49    pub predicate: F,
50
51    /// A name for this filter, used in error messages.
52    pub filter_name: &'static str,
53}
54
55impl<D, F> FilterDataProvider<D, F>
56where
57    F: Fn(DataIdentifierBorrowed) -> bool,
58{
59    fn check(&self, marker: DataMarkerInfo, req: DataRequest) -> Result<(), DataError> {
60        if !(self.predicate)(req.id) {
61            return Err(DataErrorKind::IdentifierNotFound
62                .with_str_context(self.filter_name)
63                .with_req(marker, req));
64        }
65        Ok(())
66    }
67}
68
69impl<D, F, M> DynamicDataProvider<M> for FilterDataProvider<D, F>
70where
71    F: Fn(DataIdentifierBorrowed) -> bool,
72    M: DynamicDataMarker,
73    D: DynamicDataProvider<M>,
74{
75    fn load_data(
76        &self,
77        marker: DataMarkerInfo,
78        req: DataRequest,
79    ) -> Result<DataResponse<M>, DataError> {
80        self.check(marker, req)?;
81        self.inner.load_data(marker, req)
82    }
83}
84
85impl<D, F, M> DynamicDryDataProvider<M> for FilterDataProvider<D, F>
86where
87    F: Fn(DataIdentifierBorrowed) -> bool,
88    M: DynamicDataMarker,
89    D: DynamicDryDataProvider<M>,
90{
91    fn dry_load_data(
92        &self,
93        marker: DataMarkerInfo,
94        req: DataRequest,
95    ) -> Result<DataResponseMetadata, DataError> {
96        self.check(marker, req)?;
97        self.inner.dry_load_data(marker, req)
98    }
99}
100
101impl<D, F, M> DataProvider<M> for FilterDataProvider<D, F>
102where
103    F: Fn(DataIdentifierBorrowed) -> bool,
104    M: DataMarker,
105    D: DataProvider<M>,
106{
107    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
108        self.check(M::INFO, req)?;
109        self.inner.load(req)
110    }
111}
112
113impl<D, F, M> DryDataProvider<M> for FilterDataProvider<D, F>
114where
115    F: Fn(DataIdentifierBorrowed) -> bool,
116    M: DataMarker,
117    D: DryDataProvider<M>,
118{
119    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError> {
120        self.check(M::INFO, req)?;
121        self.inner.dry_load(req)
122    }
123}
124
125impl<M, D, F> IterableDynamicDataProvider<M> for FilterDataProvider<D, F>
126where
127    M: DynamicDataMarker,
128    F: Fn(DataIdentifierBorrowed) -> bool,
129    D: IterableDynamicDataProvider<M>,
130{
131    fn iter_ids_for_marker(
132        &self,
133        marker: DataMarkerInfo,
134    ) -> Result<BTreeSet<DataIdentifierCow<'_>>, DataError> {
135        self.inner.iter_ids_for_marker(marker).map(|set| {
136            // Use filter_map instead of filter to avoid cloning the locale
137            set.into_iter()
138                .filter(|id| (self.predicate)(id.as_borrowed()))
139                .collect()
140        })
141    }
142}
143
144impl<M, D, F> IterableDataProvider<M> for FilterDataProvider<D, F>
145where
146    M: DataMarker,
147    F: Fn(DataIdentifierBorrowed) -> bool,
148    D: IterableDataProvider<M>,
149{
150    fn iter_ids(&self) -> Result<BTreeSet<DataIdentifierCow<'_>>, DataError> {
151        self.inner.iter_ids().map(|vec| {
152            // Use filter_map instead of filter to avoid cloning the locale
153            vec.into_iter()
154                .filter(|id| (self.predicate)(id.as_borrowed()))
155                .collect()
156        })
157    }
158}
159
160#[cfg(feature = "export")]
161impl<P0, F> ExportableProvider for FilterDataProvider<P0, F>
162where
163    P0: ExportableProvider,
164    F: Fn(DataIdentifierBorrowed) -> bool + Sync,
165{
166    fn supported_markers(&self) -> alloc::collections::BTreeSet<DataMarkerInfo> {
167        // The predicate only takes DataIdentifier, not DataMarker, so we are not impacted
168        self.inner.supported_markers()
169    }
170}