summaryrefslogtreecommitdiffstats
path: root/vendor/icu_provider_adapters/src/filter
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/icu_provider_adapters/src/filter')
-rw-r--r--vendor/icu_provider_adapters/src/filter/impls.rs215
-rw-r--r--vendor/icu_provider_adapters/src/filter/mod.rs242
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,
+ }
+ }
+}