diff options
Diffstat (limited to 'vendor/icu_provider_adapters/src/filter')
-rw-r--r-- | vendor/icu_provider_adapters/src/filter/impls.rs | 215 | ||||
-rw-r--r-- | vendor/icu_provider_adapters/src/filter/mod.rs | 242 |
2 files changed, 457 insertions, 0 deletions
diff --git a/vendor/icu_provider_adapters/src/filter/impls.rs b/vendor/icu_provider_adapters/src/filter/impls.rs new file mode 100644 index 000000000..af8ea7adf --- /dev/null +++ b/vendor/icu_provider_adapters/src/filter/impls.rs @@ -0,0 +1,215 @@ +// 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 ). + +use super::*; +use alloc::boxed::Box; +use icu_provider::prelude::*; + +use icu_locid::LanguageIdentifier; + +impl<D, F> RequestFilterDataProvider<D, F> +where + F: Fn(DataRequest) -> bool + Sync, +{ + /// Filter out data requests with certain langids according to the predicate function. The + /// predicate should return `true` to allow a langid and `false` to reject a langid. + /// + /// Data requests with no langid will be allowed. To reject data requests without a langid, + /// chain this with [`Self::require_langid`]. + /// + /// # Examples + /// + /// ``` + /// use icu_locid::LanguageIdentifier; + /// use icu_locid::{langid, locale, subtags_language as language}; + /// use icu_provider::datagen::*; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// use icu_provider_adapters::filter::Filterable; + /// + /// let provider = HelloWorldProvider + /// .filterable("Demo no-English filter") + /// .filter_by_langid(|langid| langid.language != language!("en")); + /// + /// // German requests should succeed: + /// let req_de = DataRequest { + /// locale: &locale!("de").into(), + /// metadata: Default::default(), + /// }; + /// let response: Result<DataResponse<HelloWorldV1Marker>, _> = + /// provider.load(req_de); + /// assert!(matches!(response, Ok(_))); + /// + /// // English requests should fail: + /// let req_en = DataRequest { + /// locale: &locale!("en-US").into(), + /// metadata: Default::default(), + /// }; + /// let response: Result<DataResponse<HelloWorldV1Marker>, _> = + /// provider.load(req_en); + /// assert!(matches!( + /// response, + /// Err(DataError { + /// kind: DataErrorKind::FilteredResource, + /// .. + /// }) + /// )); + /// + /// // English should not appear in the iterator result: + /// let supported_langids = provider + /// .supported_locales() + /// .expect("Should successfully make an iterator of supported locales") + /// .into_iter() + /// .map(|options| options.get_langid()) + /// .collect::<Vec<LanguageIdentifier>>(); + /// assert!(supported_langids.contains(&langid!("de"))); + /// assert!(!supported_langids.contains(&langid!("en"))); + /// ``` + pub fn filter_by_langid<'a>( + self, + predicate: impl Fn(&LanguageIdentifier) -> bool + Sync + 'a, + ) -> RequestFilterDataProvider<D, Box<dyn Fn(DataRequest) -> bool + Sync + 'a>> + where + F: 'a, + { + let old_predicate = self.predicate; + RequestFilterDataProvider { + inner: self.inner, + predicate: Box::new(move |request| -> bool { + if !(old_predicate)(request) { + return false; + } + predicate(&request.locale.get_langid()) + }), + filter_name: self.filter_name, + } + } + + /// Filter out data request except those having a language identifier that exactly matches + /// one in the allowlist. + /// + /// This will be replaced with a smarter algorithm for locale filtering; see + /// <https://github.com/unicode-org/icu4x/issues/834> + /// + /// Data requests with no langid will be allowed. To reject data requests without a langid, + /// chain this with [`Self::require_langid`]. + /// + /// # Examples + /// + /// ``` + /// use icu_locid::{langid, locale}; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// use icu_provider_adapters::filter::Filterable; + /// + /// let allowlist = vec![langid!("de"), langid!("zh")]; + /// let provider = HelloWorldProvider + /// .filterable("Demo German+Chinese filter") + /// .filter_by_langid_allowlist_strict(&allowlist); + /// + /// // German requests should succeed: + /// let req_de = DataRequest { + /// locale: &locale!("de").into(), + /// metadata: Default::default(), + /// }; + /// let response: Result<DataResponse<HelloWorldV1Marker>, _> = + /// provider.load(req_de); + /// assert!(matches!(response, Ok(_))); + /// + /// // English requests should fail: + /// let req_en = DataRequest { + /// locale: &locale!("en-US").into(), + /// metadata: Default::default(), + /// }; + /// let response: Result<DataResponse<HelloWorldV1Marker>, _> = + /// provider.load(req_en); + /// assert!(matches!( + /// response, + /// Err(DataError { + /// kind: DataErrorKind::FilteredResource, + /// .. + /// }) + /// )); + /// assert_eq!( + /// response.unwrap_err().str_context, + /// Some("Demo German+Chinese filter") + /// ); + /// ``` + pub fn filter_by_langid_allowlist_strict<'a>( + self, + allowlist: &'a [LanguageIdentifier], + ) -> RequestFilterDataProvider<D, Box<dyn Fn(DataRequest) -> bool + Sync + 'a>> + where + F: 'a, + { + let old_predicate = self.predicate; + RequestFilterDataProvider { + inner: self.inner, + predicate: Box::new(move |request| -> bool { + if !(old_predicate)(request) { + return false; + } + request.locale.is_langid_und() || allowlist.contains(&request.locale.get_langid()) + }), + filter_name: self.filter_name, + } + } + + /// Require that data requests contain a langid. + /// + /// # Examples + /// + /// ``` + /// use icu_locid::locale; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// use icu_provider_adapters::filter::Filterable; + /// + /// let provider = HelloWorldProvider + /// .filterable("Demo require-langid filter") + /// .require_langid(); + /// + /// // Requests with a langid should succeed: + /// let req_with_langid = DataRequest { + /// locale: &locale!("de").into(), + /// metadata: Default::default(), + /// }; + /// let response: Result<DataResponse<HelloWorldV1Marker>, _> = + /// provider.load(req_with_langid); + /// assert!(matches!(response, Ok(_))); + /// + /// // Requests without a langid should fail: + /// let req_no_langid = DataRequest { + /// locale: Default::default(), + /// metadata: Default::default(), + /// }; + /// let response: Result<DataResponse<HelloWorldV1Marker>, _> = + /// provider.load(req_no_langid); + /// assert!(matches!( + /// response, + /// Err(DataError { + /// kind: DataErrorKind::FilteredResource, + /// .. + /// }) + /// )); + /// ``` + pub fn require_langid<'a>( + self, + ) -> RequestFilterDataProvider<D, Box<dyn Fn(DataRequest) -> bool + Sync + 'a>> + where + F: 'a, + { + let old_predicate = self.predicate; + RequestFilterDataProvider { + inner: self.inner, + predicate: Box::new(move |request| -> bool { + if !(old_predicate)(request) { + return false; + } + !request.locale.is_langid_und() + }), + filter_name: self.filter_name, + } + } +} diff --git a/vendor/icu_provider_adapters/src/filter/mod.rs b/vendor/icu_provider_adapters/src/filter/mod.rs new file mode 100644 index 000000000..42516b82e --- /dev/null +++ b/vendor/icu_provider_adapters/src/filter/mod.rs @@ -0,0 +1,242 @@ +// 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<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, + } + } +} |