blob: 19b6bf2335ab64c3f04a257bb379e97ed191d3b0 [file] [log] [blame]
// 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,
}
}
}