diff options
Diffstat (limited to 'third_party/rust/icu_provider_adapters/src/filter/mod.rs')
-rw-r--r-- | third_party/rust/icu_provider_adapters/src/filter/mod.rs | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/third_party/rust/icu_provider_adapters/src/filter/mod.rs b/third_party/rust/icu_provider_adapters/src/filter/mod.rs new file mode 100644 index 0000000000..19b6bf2335 --- /dev/null +++ b/third_party/rust/icu_provider_adapters/src/filter/mod.rs @@ -0,0 +1,243 @@ +// 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, + } + } +} |