| // 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; |
| //! 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 |
| #[derive(Debug)] |
| pub struct RequestFilterDataProvider<D, F> |
| 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<D, F, M> DynamicDataProvider<M> for RequestFilterDataProvider<D, F> |
| where |
| F: Fn(DataRequest) -> bool, |
| M: DataMarker, |
| D: DynamicDataProvider<M>, |
| { |
| fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, 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<D, F, M> DataProvider<M> for RequestFilterDataProvider<D, F> |
| where |
| F: Fn(DataRequest) -> bool, |
| M: KeyedDataMarker, |
| D: DataProvider<M>, |
| { |
| fn load(&self, req: DataRequest) -> Result<DataResponse<M>, 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<D, F> BufferProvider for RequestFilterDataProvider<D, F> |
| where |
| F: Fn(DataRequest) -> bool, |
| D: BufferProvider, |
| { |
| fn load_buffer( |
| &self, |
| key: DataKey, |
| req: DataRequest, |
| ) -> Result<DataResponse<BufferMarker>, 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<D, F> AnyProvider for RequestFilterDataProvider<D, F> |
| where |
| F: Fn(DataRequest) -> bool, |
| D: AnyProvider, |
| { |
| fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> { |
| 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<M, D, F> datagen::IterableDynamicDataProvider<M> for RequestFilterDataProvider<D, F> |
| where |
| M: DataMarker, |
| F: Fn(DataRequest) -> bool, |
| D: datagen::IterableDynamicDataProvider<M>, |
| { |
| fn supported_locales_for_key( |
| &self, |
| key: DataKey, |
| ) -> Result<alloc::vec::Vec<DataLocale>, 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<M, D, F> datagen::IterableDataProvider<M> for RequestFilterDataProvider<D, F> |
| where |
| M: KeyedDataMarker, |
| F: Fn(DataRequest) -> bool, |
| D: datagen::IterableDataProvider<M>, |
| { |
| fn supported_locales(&self) -> Result<alloc::vec::Vec<DataLocale>, 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<D, F, MFrom, MTo> datagen::DataConverter<MFrom, MTo> for RequestFilterDataProvider<D, F> |
| where |
| D: datagen::DataConverter<MFrom, MTo>, |
| MFrom: DataMarker, |
| MTo: DataMarker, |
| F: Fn(DataRequest) -> bool, |
| { |
| fn convert( |
| &self, |
| key: DataKey, |
| from: DataPayload<MFrom>, |
| ) -> Result<DataPayload<MTo>, (DataPayload<MFrom>, 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<Self, fn(DataRequest) -> bool>; |
| } |
| |
| impl<T> Filterable for T |
| where |
| T: Sized, |
| { |
| fn filterable( |
| self, |
| filter_name: &'static str, |
| ) -> RequestFilterDataProvider<Self, fn(DataRequest) -> bool> { |
| fn noop(_: DataRequest) -> bool { |
| true |
| } |
| RequestFilterDataProvider { |
| inner: self, |
| predicate: noop, |
| filter_name, |
| } |
| } |
| } |