// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). //! Providers that filter resource requests. //! //! Requests that fail a filter test will return [`DataError`] of kind [`FilteredResource`]( //! DataErrorKind::FilteredResource) and will not appear in [`IterableDynamicDataProvider`] iterators. //! //! The main struct is [`RequestFilterDataProvider`]. Although that struct can be created //! directly, the traits in this module provide helper functions for common filtering patterns. //! //! To create a `RequestFilterDataProvider`, you can use the [`Filterable`] blanket function: //! //! ``` //! use icu_provider_adapters::filter::Filterable; //! //! // now call .filterable() on any object to get a RequestFilterDataProvider //! ``` //! //! # Examples //! //! ``` //! use icu_locid::subtags_language as language; //! use icu_provider::hello_world::*; //! use icu_provider::prelude::*; //! use icu_provider_adapters::filter::Filterable; //! //! // Only return German data from a HelloWorldProvider: //! HelloWorldProvider //! .filterable("Demo German-only filter") //! .filter_by_langid(|langid| langid.language == language!("de")); //! ``` //! //! [`IterableDynamicDataProvider`]: icu_provider::datagen::IterableDynamicDataProvider mod impls; pub use impls::*; #[cfg(feature = "datagen")] use icu_provider::datagen; use icu_provider::prelude::*; /// A data provider that selectively filters out data requests. /// /// Data requests that are rejected by the filter will return a [`DataError`] with kind /// [`FilteredResource`](DataErrorKind::FilteredResource), and they will not be returned /// by [`datagen::IterableDynamicDataProvider::supported_locales_for_key`]. /// /// Although this struct can be created directly, the traits in this module provide helper /// functions for common filtering patterns. #[allow(clippy::exhaustive_structs)] // this type is stable pub struct RequestFilterDataProvider where F: Fn(DataRequest) -> bool, { /// The data provider to which we delegate requests. pub inner: D, /// The predicate function. A return value of `true` indicates that the request should /// proceed as normal; a return value of `false` will reject the request. pub predicate: F, /// A name for this filter, used in error messages. pub filter_name: &'static str, } impl DynamicDataProvider for RequestFilterDataProvider where F: Fn(DataRequest) -> bool, M: DataMarker, D: DynamicDataProvider, { fn load_data(&self, key: DataKey, req: DataRequest) -> Result, DataError> { if (self.predicate)(req) { self.inner.load_data(key, req) } else { Err(DataErrorKind::FilteredResource .with_str_context(self.filter_name) .with_req(key, req)) } } } impl DataProvider for RequestFilterDataProvider where F: Fn(DataRequest) -> bool, M: KeyedDataMarker, D: DataProvider, { fn load(&self, req: DataRequest) -> Result, DataError> { if (self.predicate)(req) { self.inner.load(req) } else { Err(DataErrorKind::FilteredResource .with_str_context(self.filter_name) .with_req(M::KEY, req)) } } } impl BufferProvider for RequestFilterDataProvider where F: Fn(DataRequest) -> bool, D: BufferProvider, { fn load_buffer( &self, key: DataKey, req: DataRequest, ) -> Result, DataError> { if (self.predicate)(req) { self.inner.load_buffer(key, req) } else { Err(DataErrorKind::FilteredResource .with_str_context(self.filter_name) .with_req(key, req)) } } } impl AnyProvider for RequestFilterDataProvider where F: Fn(DataRequest) -> bool, D: AnyProvider, { fn load_any(&self, key: DataKey, req: DataRequest) -> Result { if (self.predicate)(req) { self.inner.load_any(key, req) } else { Err(DataErrorKind::FilteredResource .with_str_context(self.filter_name) .with_req(key, req)) } } } #[cfg(feature = "datagen")] impl datagen::IterableDynamicDataProvider for RequestFilterDataProvider where M: DataMarker, F: Fn(DataRequest) -> bool, D: datagen::IterableDynamicDataProvider, { fn supported_locales_for_key( &self, key: DataKey, ) -> Result, DataError> { self.inner.supported_locales_for_key(key).map(|vec| { // Use filter_map instead of filter to avoid cloning the locale vec.into_iter() .filter_map(|locale| { if (self.predicate)(DataRequest { locale: &locale, metadata: Default::default(), }) { Some(locale) } else { None } }) .collect() }) } } #[cfg(feature = "datagen")] impl datagen::IterableDataProvider for RequestFilterDataProvider where M: KeyedDataMarker, F: Fn(DataRequest) -> bool, D: datagen::IterableDataProvider, { fn supported_locales(&self) -> Result, DataError> { self.inner.supported_locales().map(|vec| { // Use filter_map instead of filter to avoid cloning the locale vec.into_iter() .filter_map(|locale| { if (self.predicate)(DataRequest { locale: &locale, metadata: Default::default(), }) { Some(locale) } else { None } }) .collect() }) } } #[cfg(feature = "datagen")] impl datagen::DataConverter for RequestFilterDataProvider where D: datagen::DataConverter, MFrom: DataMarker, MTo: DataMarker, F: Fn(DataRequest) -> bool, { fn convert( &self, key: DataKey, from: DataPayload, ) -> Result, (DataPayload, DataError)> { // Conversions are type-agnostic self.inner.convert(key, from) } } /// A blanket-implemented trait exposing the [`Self::filterable()`] function. /// /// For more details, see [`icu_provider_adapters::filter`](crate::filter). pub trait Filterable: Sized { /// Creates a filterable data provider with the given name for debugging. /// /// For more details, see [`icu_provider_adapters::filter`](crate::filter). fn filterable( self, filter_name: &'static str, ) -> RequestFilterDataProvider bool>; } impl Filterable for T where T: Sized, { fn filterable( self, filter_name: &'static str, ) -> RequestFilterDataProvider bool> { fn noop(_: DataRequest) -> bool { true } RequestFilterDataProvider { inner: self, predicate: noop, filter_name, } } }