diff options
Diffstat (limited to 'vendor/icu_provider_adapters')
23 files changed, 2905 insertions, 0 deletions
diff --git a/vendor/icu_provider_adapters/.cargo-checksum.json b/vendor/icu_provider_adapters/.cargo-checksum.json new file mode 100644 index 000000000..82805760b --- /dev/null +++ b/vendor/icu_provider_adapters/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"5ecb7cc7b9f229262dc3694db154ab479591430d0d10e03b5aed677067c16d09","LICENSE":"4ad7541d66a407234e2c84902124cef325c29f3e966353efdb800bedb8b8da21","README.md":"e6a08c2bc307b66c93de20c2c366da896f7fd3f5e5eb402edd2eeba79476b600","src/any_payload.rs":"55336b0a861be2da125a411924a3597de5593135c8ff589420237189b77311f8","src/either.rs":"aa309c26deafcb9470bb9eb15e629fc53f2fb5d7e412d1a2317bc170f0652588","src/empty.rs":"2ef71bb0eb3b81cf8cbb9f93c37bb0b7a106b4db941ffdefc8e88b944bee90d2","src/fallback/adapter.rs":"31d851e1d2adda59c06510cdfc43846a107ee706b731b3019b7c28571f0398ff","src/fallback/algorithms.rs":"bc8d17fb957f8f9ada8e13d6f0d35af248caa621441ffb3fcd273ff97e39e4a4","src/fallback/mod.rs":"ec165267f61c722300cc4a44be7561e31a5c65007f8a4c11fa074f31d768797a","src/fallback/provider.rs":"ef8a835e3ed2b7871b6f6e78ae2e73575b26adc9e8e0b5a7170c8513e7fe3269","src/filter/impls.rs":"58fcc1f03454769a3c11a92cb4b7b9f7833a0c042ea80b4e863609e2414eed0f","src/filter/mod.rs":"960e1c22d7d2323bf73c3ff6ea2d34158ff767f2a6b4fc8c40e982bf8c2dd500","src/fork/by_error.rs":"67742b78a80ff9765fd83fc28390687ed6e07f71afc0964b20221ff1f508e380","src/fork/macros.rs":"5aebb0134923fa9fa0fe7c16d2d85a1a29cf4ea49505187db56bf0bc8e984ad0","src/fork/mod.rs":"7378895128bf1ccc9de7f5675668ef922d3df7b84b04f68af4d5b49efe60341e","src/fork/predicates.rs":"314bf33144c6827cd3df05877d7c20fb64c9b18f6230e229340b8f04f4ccf4d3","src/helpers.rs":"008af3aa36ebf43ec249dd9162ff36c150fa00fe78b3065b023d59acbe218b7b","src/lib.rs":"7499b4bcb40453bc4678d6ae3febf05f5c1e351120513ddd694983e6da56f268","tests/data/langtest/de/core/helloworld@1/de.json":"5a45b1d80567de8c4ff754f7dbe20c22477c1e00a9400b38397eb034f295b8f8","tests/data/langtest/de/manifest.json":"fa2f848cff051fd12a909389fbbc44b93ae1feb92cce466cc4381f9548443ea9","tests/data/langtest/ro/core/helloworld@1/ro.json":"b6b68292746dd6bb2d92d9c08e2753db556ec9c954a3b593b0b8999df550f298","tests/data/langtest/ro/manifest.json":"fa2f848cff051fd12a909389fbbc44b93ae1feb92cce466cc4381f9548443ea9"},"package":"980c71d8a91b246ebbb97847178a4b816eea39d1d550c70ee566384555bb6545"}
\ No newline at end of file diff --git a/vendor/icu_provider_adapters/Cargo.toml b/vendor/icu_provider_adapters/Cargo.toml new file mode 100644 index 000000000..365a2a69b --- /dev/null +++ b/vendor/icu_provider_adapters/Cargo.toml @@ -0,0 +1,88 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "icu_provider_adapters" +version = "1.0.0" +authors = ["The ICU4X Project Developers"] +include = [ + "src/**/*", + "examples/**/*", + "benches/**/*", + "tests/**/*", + "Cargo.toml", + "LICENSE", + "README.md", +] +description = "Adapters for composing and manipulating data providers." +readme = "README.md" +categories = ["internationalization"] +license = "Unicode-DFS-2016" +repository = "https://github.com/unicode-org/icu4x" +resolver = "2" + +[dependencies.databake] +version = "0.1.0" +features = ["derive"] +optional = true + +[dependencies.icu_locid] +version = "1.0.0" +features = ["zerovec"] + +[dependencies.icu_provider] +version = "1.0.0" +features = ["macros"] + +[dependencies.serde] +version = "1.0" +features = [ + "derive", + "alloc", +] +optional = true +default-features = false + +[dependencies.tinystr] +version = "0.7" +features = ["zerovec"] + +[dependencies.yoke] +version = "0.6" + +[dependencies.zerovec] +version = "0.9" +features = ["yoke"] + +[dev-dependencies.icu_provider] +version = "1.0.0" +features = [ + "macros", + "deserialize_json", +] + +[features] +datagen = [ + "std", + "serde", + "databake", + "icu_provider/datagen", + "icu_locid/databake", + "zerovec/databake", +] +serde = [ + "dep:serde", + "zerovec/serde", + "icu_locid/serde", + "icu_provider/serde", +] +std = ["icu_locid/std"] diff --git a/vendor/icu_provider_adapters/LICENSE b/vendor/icu_provider_adapters/LICENSE new file mode 100644 index 000000000..9858d01ab --- /dev/null +++ b/vendor/icu_provider_adapters/LICENSE @@ -0,0 +1,51 @@ +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +See Terms of Use <https://www.unicode.org/copyright.html> +for definitions of Unicode Inc.’s Data Files and Software. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2022 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. diff --git a/vendor/icu_provider_adapters/README.md b/vendor/icu_provider_adapters/README.md new file mode 100644 index 000000000..28a29e6bc --- /dev/null +++ b/vendor/icu_provider_adapters/README.md @@ -0,0 +1,12 @@ +# icu_provider_adapters [![crates.io](https://img.shields.io/crates/v/icu_provider_adapters)](https://crates.io/crates/icu_provider_adapters) + +Adapters for composing and manipulating data providers. + +- Use the [`fork`] module to marshall data requests between multiple possible providers. +- Use the [`either`] module to choose between multiple provider types at runtime. +- Use the [`filter`] module to programmatically reject certain data requests. +- Use the [`fallback`] module to automatically resolve arbitrary locales for data loading. + +## More Information + +For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x). diff --git a/vendor/icu_provider_adapters/src/any_payload.rs b/vendor/icu_provider_adapters/src/any_payload.rs new file mode 100644 index 000000000..bbb9220c3 --- /dev/null +++ b/vendor/icu_provider_adapters/src/any_payload.rs @@ -0,0 +1,123 @@ +// 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 ). + +//! Data provider always serving the same struct. + +use icu_provider::prelude::*; +use yoke::trait_hack::YokeTraitHack; +use yoke::Yokeable; +use zerofrom::ZeroFrom; + +/// A data provider that returns clones of a fixed type-erased payload. +/// +/// [`AnyPayloadProvider`] implements [`AnyProvider`], so it can be used in +/// `*_with_any_provider` constructors across ICU4X. +/// +/// # Examples +/// +/// ``` +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// use icu_provider_adapters::any_payload::AnyPayloadProvider; +/// use std::borrow::Cow; +/// use writeable::assert_writeable_eq; +/// +/// let provider = +/// AnyPayloadProvider::from_static::<HelloWorldV1Marker>(&HelloWorldV1 { +/// message: Cow::Borrowed("custom hello world"), +/// }); +/// +/// // Check that it works: +/// let formatter = HelloWorldFormatter::try_new_with_any_provider( +/// &provider, +/// &icu_locid::Locale::UND.into(), +/// ) +/// .expect("key matches"); +/// assert_writeable_eq!(formatter.format(), "custom hello world"); +/// +/// // Requests for invalid keys get MissingDataKey +/// assert!(matches!( +/// provider.load_any(icu_provider::data_key!("foo@1"), Default::default()), +/// Err(DataError { +/// kind: DataErrorKind::MissingDataKey, +/// .. +/// }) +/// )) +/// ``` +#[allow(clippy::exhaustive_structs)] // this type is stable +pub struct AnyPayloadProvider { + /// The [`DataKey`] for which to provide data. All others will receive a + /// [`DataErrorKind::MissingDataKey`]. + key: DataKey, + /// The [`AnyPayload`] to return on matching requests. + data: AnyPayload, +} + +impl AnyPayloadProvider { + /// Creates an `AnyPayloadProvider` with an owned (allocated) payload of the given data. + pub fn from_owned<M: KeyedDataMarker + 'static>(data: M::Yokeable) -> Self + where + M::Yokeable: icu_provider::MaybeSendSync, + { + Self::from_payload::<M>(DataPayload::from_owned(data)) + } + + /// Creates an `AnyPayloadProvider` with a statically borrowed payload of the given data. + pub fn from_static<M: KeyedDataMarker>(data: &'static M::Yokeable) -> Self { + AnyPayloadProvider { + key: M::KEY, + data: AnyPayload::from_static_ref(data), + } + } + + /// Creates an `AnyPayloadProvider` from an existing [`DataPayload`]. + pub fn from_payload<M: KeyedDataMarker + 'static>(payload: DataPayload<M>) -> Self + where + M::Yokeable: icu_provider::MaybeSendSync, + { + AnyPayloadProvider { + key: M::KEY, + data: payload.wrap_into_any_payload(), + } + } + + /// Creates an `AnyPayloadProvider` from an existing [`AnyPayload`]. + pub fn from_any_payload<M: KeyedDataMarker + 'static>(payload: AnyPayload) -> Self { + AnyPayloadProvider { + key: M::KEY, + data: payload, + } + } + + /// Creates an `AnyPayloadProvider` with the default (allocated) version of the data struct. + pub fn new_default<M: KeyedDataMarker + 'static>() -> Self + where + M::Yokeable: Default, + M::Yokeable: icu_provider::MaybeSendSync, + { + Self::from_owned::<M>(M::Yokeable::default()) + } +} + +impl AnyProvider for AnyPayloadProvider { + fn load_any(&self, key: DataKey, _: DataRequest) -> Result<AnyResponse, DataError> { + key.match_key(self.key)?; + Ok(AnyResponse { + metadata: DataResponseMetadata::default(), + payload: Some(self.data.clone()), + }) + } +} + +impl<M> DataProvider<M> for AnyPayloadProvider +where + M: KeyedDataMarker + 'static, + for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone, + M::Yokeable: ZeroFrom<'static, M::Yokeable>, + M::Yokeable: icu_provider::MaybeSendSync, +{ + fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { + self.as_downcasting().load(req) + } +} diff --git a/vendor/icu_provider_adapters/src/either.rs b/vendor/icu_provider_adapters/src/either.rs new file mode 100644 index 000000000..b62a9c3fb --- /dev/null +++ b/vendor/icu_provider_adapters/src/either.rs @@ -0,0 +1,111 @@ +// 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 ). + +//! Helpers for switching between multiple providers. + +use icu_provider::prelude::*; + +#[cfg(feature = "datagen")] +use icu_provider::datagen; + +/// A provider that is one of two types determined at runtime. +/// +/// Data provider traits implemented by both `P0` and `P1` are implemented on +/// `EitherProvider<P0, P1>`. +#[allow(clippy::exhaustive_enums)] // this is stable +pub enum EitherProvider<P0, P1> { + /// A value of type `P0`. + A(P0), + /// A value of type `P1`. + B(P1), +} + +impl<P0: AnyProvider, P1: AnyProvider> AnyProvider for EitherProvider<P0, P1> { + #[inline] + fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> { + use EitherProvider::*; + match self { + A(p) => p.load_any(key, req), + B(p) => p.load_any(key, req), + } + } +} + +impl<P0: BufferProvider, P1: BufferProvider> BufferProvider for EitherProvider<P0, P1> { + #[inline] + fn load_buffer( + &self, + key: DataKey, + req: DataRequest, + ) -> Result<DataResponse<BufferMarker>, DataError> { + use EitherProvider::*; + match self { + A(p) => p.load_buffer(key, req), + B(p) => p.load_buffer(key, req), + } + } +} + +impl<M: DataMarker, P0: DynamicDataProvider<M>, P1: DynamicDataProvider<M>> DynamicDataProvider<M> + for EitherProvider<P0, P1> +{ + #[inline] + fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { + use EitherProvider::*; + match self { + A(p) => p.load_data(key, req), + B(p) => p.load_data(key, req), + } + } +} + +impl<M: KeyedDataMarker, P0: DataProvider<M>, P1: DataProvider<M>> DataProvider<M> + for EitherProvider<P0, P1> +{ + #[inline] + fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { + use EitherProvider::*; + match self { + A(p) => p.load(req), + B(p) => p.load(req), + } + } +} + +#[cfg(feature = "datagen")] +impl< + M: DataMarker, + P0: datagen::IterableDynamicDataProvider<M>, + P1: datagen::IterableDynamicDataProvider<M>, + > datagen::IterableDynamicDataProvider<M> for EitherProvider<P0, P1> +{ + #[inline] + fn supported_locales_for_key( + &self, + key: DataKey, + ) -> Result<alloc::vec::Vec<DataLocale>, DataError> { + use EitherProvider::*; + match self { + A(p) => p.supported_locales_for_key(key), + B(p) => p.supported_locales_for_key(key), + } + } +} + +#[cfg(feature = "datagen")] +impl< + M: KeyedDataMarker, + P0: datagen::IterableDataProvider<M>, + P1: datagen::IterableDataProvider<M>, + > datagen::IterableDataProvider<M> for EitherProvider<P0, P1> +{ + #[inline] + fn supported_locales(&self) -> Result<alloc::vec::Vec<DataLocale>, DataError> { + use EitherProvider::*; + match self { + A(p) => p.supported_locales(), + B(p) => p.supported_locales(), + } + } +} diff --git a/vendor/icu_provider_adapters/src/empty.rs b/vendor/icu_provider_adapters/src/empty.rs new file mode 100644 index 000000000..31a6c42aa --- /dev/null +++ b/vendor/icu_provider_adapters/src/empty.rs @@ -0,0 +1,88 @@ +// 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 ). + +//! Empty data provider implementations. +//! +//! Use [`EmptyDataProvider`] as a stand-in for a provider that always fails. + +use icu_provider::prelude::*; + +/// A data provider that always returns an error. +/// +/// The returned error kind is configurable. +/// +/// # Examples +/// +/// ``` +/// use icu_provider::hello_world::HelloWorldV1Marker; +/// use icu_provider::prelude::*; +/// use icu_provider_adapters::empty::EmptyDataProvider; +/// +/// let provider = EmptyDataProvider::new(); +/// +/// assert!(matches!( +/// provider.load_any(HelloWorldV1Marker::KEY, Default::default()), +/// Err(DataError { +/// kind: DataErrorKind::MissingDataKey, +/// .. +/// }) +/// )); +/// ``` +pub struct EmptyDataProvider { + error_kind: DataErrorKind, +} + +impl Default for EmptyDataProvider { + fn default() -> Self { + Self::new() + } +} + +impl EmptyDataProvider { + /// Creates a data provider that always returns [`DataErrorKind::MissingDataKey`]. + pub fn new() -> Self { + Self { + error_kind: DataErrorKind::MissingDataKey, + } + } + + /// Creates a data provider that always returns the specified error kind. + pub fn new_with_error_kind(error_kind: DataErrorKind) -> Self { + Self { error_kind } + } +} + +impl AnyProvider for EmptyDataProvider { + fn load_any(&self, key: DataKey, base_req: DataRequest) -> Result<AnyResponse, DataError> { + Err(self.error_kind.with_req(key, base_req)) + } +} + +impl BufferProvider for EmptyDataProvider { + fn load_buffer( + &self, + key: DataKey, + base_req: DataRequest, + ) -> Result<DataResponse<BufferMarker>, DataError> { + Err(self.error_kind.with_req(key, base_req)) + } +} + +impl<M> DynamicDataProvider<M> for EmptyDataProvider +where + M: DataMarker, +{ + fn load_data(&self, key: DataKey, base_req: DataRequest) -> Result<DataResponse<M>, DataError> { + Err(self.error_kind.with_req(key, base_req)) + } +} + +impl<M> DataProvider<M> for EmptyDataProvider +where + M: KeyedDataMarker, +{ + fn load(&self, base_req: DataRequest) -> Result<DataResponse<M>, DataError> { + Err(self.error_kind.with_req(M::KEY, base_req)) + } +} diff --git a/vendor/icu_provider_adapters/src/fallback/adapter.rs b/vendor/icu_provider_adapters/src/fallback/adapter.rs new file mode 100644 index 000000000..4d1f79255 --- /dev/null +++ b/vendor/icu_provider_adapters/src/fallback/adapter.rs @@ -0,0 +1,273 @@ +// 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 crate::helpers::result_is_err_missing_data_options; + +/// A data provider wrapper that performs locale fallback. This enables arbitrary locales to be +/// handled at runtime. +/// +/// # Examples +/// +/// ``` +/// use icu_locid::locale; +/// use icu_provider::prelude::*; +/// use icu_provider::hello_world::*; +/// use icu_provider_adapters::fallback::LocaleFallbackProvider; +/// +/// let provider = icu_testdata::unstable_no_fallback(); +/// +/// let req = DataRequest { +/// locale: &locale!("ja-JP").into(), +/// metadata: Default::default(), +/// }; +/// +/// // The provider does not have data for "ja-JP": +/// DataProvider::<HelloWorldV1Marker>::load(&provider, req).expect_err("No fallback"); +/// +/// // But if we wrap the provider in a fallback provider... +/// let provider = LocaleFallbackProvider::try_new_unstable(provider) +/// .expect("Fallback data present"); +/// +/// // ...then we can load "ja-JP" based on "ja" data +/// let response = +/// DataProvider::<HelloWorldV1Marker>::load(&provider, req).expect("successful with vertical fallback"); +/// +/// assert_eq!( +/// "ja", +/// response.metadata.locale.unwrap().to_string() +/// ); +/// assert_eq!( +/// "こんにちは世界", +/// response.payload.unwrap().get().message +/// ); +/// ``` +pub struct LocaleFallbackProvider<P> { + inner: P, + fallbacker: LocaleFallbacker, +} + +impl<P> LocaleFallbackProvider<P> +where + P: DataProvider<LocaleFallbackLikelySubtagsV1Marker> + + DataProvider<LocaleFallbackParentsV1Marker> + + DataProvider<CollationFallbackSupplementV1Marker>, +{ + /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading + /// fallback data from it. + /// + /// If the data provider being wrapped does not contain fallback data, use + /// [`LocaleFallbackProvider::new_with_fallbacker`]. + pub fn try_new_unstable(provider: P) -> Result<Self, DataError> { + let fallbacker = LocaleFallbacker::try_new_unstable(&provider)?; + Ok(Self { + inner: provider, + fallbacker, + }) + } +} + +impl<P> LocaleFallbackProvider<P> +where + P: AnyProvider, +{ + /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading + /// fallback data from it. + /// + /// If the data provider being wrapped does not contain fallback data, use + /// [`LocaleFallbackProvider::new_with_fallbacker`]. + pub fn try_new_with_any_provider(provider: P) -> Result<Self, DataError> { + let fallbacker = LocaleFallbacker::try_new_with_any_provider(&provider)?; + Ok(Self { + inner: provider, + fallbacker, + }) + } +} + +#[cfg(feature = "serde")] +impl<P> LocaleFallbackProvider<P> +where + P: BufferProvider, +{ + /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading + /// fallback data from it. + /// + /// If the data provider being wrapped does not contain fallback data, use + /// [`LocaleFallbackProvider::new_with_fallbacker`]. + pub fn try_new_with_buffer_provider(provider: P) -> Result<Self, DataError> { + let fallbacker = LocaleFallbacker::try_new_with_buffer_provider(&provider)?; + Ok(Self { + inner: provider, + fallbacker, + }) + } +} + +impl<P> LocaleFallbackProvider<P> { + /// Wrap a provider with an arbitrary fallback engine. + /// + /// This relaxes the requirement that the wrapped provider contains its own fallback data. + /// + /// # Examples + /// + /// ``` + /// use icu_locid::locale; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// use icu_provider_adapters::fallback::{ + /// LocaleFallbackProvider, LocaleFallbacker, + /// }; + /// + /// let provider = HelloWorldProvider; + /// + /// let req = DataRequest { + /// locale: &locale!("de-CH").into(), + /// metadata: Default::default(), + /// }; + /// + /// // There is no "de-CH" data in the `HelloWorldProvider` + /// DataProvider::<HelloWorldV1Marker>::load(&provider, req) + /// .expect_err("No data for de-CH"); + /// + /// // `HelloWorldProvider` does not contain fallback data, + /// // but we can fetch it from `icu_testdata`, and then + /// // use it to create the fallbacking data provider. + /// let fallbacker = + /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()) + /// .expect("Fallback data present"); + /// let provider = + /// LocaleFallbackProvider::new_with_fallbacker(provider, fallbacker); + /// + /// // Now we can load the "de-CH" data via fallback to "de". + /// let german_hello_world: DataPayload<HelloWorldV1Marker> = provider + /// .load(req) + /// .expect("Loading should succeed") + /// .take_payload() + /// .expect("Data should be present"); + /// + /// assert_eq!("Hallo Welt", german_hello_world.get().message); + /// ``` + pub fn new_with_fallbacker(provider: P, fallbacker: LocaleFallbacker) -> Self { + Self { + inner: provider, + fallbacker, + } + } + + /// Returns a reference to the inner provider, bypassing fallback. + pub fn inner(&self) -> &P { + &self.inner + } + + /// Returns ownership of the inner provider to the caller. + pub fn into_inner(self) -> P { + self.inner + } + + /// Run the fallback algorithm with the data request using the inner data provider. + /// Internal function; external clients should use one of the trait impls below. + /// + /// Function arguments: + /// + /// - F1 should perform a data load for a single DataRequest and return the result of it + /// - F2 should map from the provider-specific response type to DataResponseMetadata + fn run_fallback<F1, F2, R>( + &self, + key: DataKey, + base_req: DataRequest, + mut f1: F1, + mut f2: F2, + ) -> Result<R, DataError> + where + F1: FnMut(DataRequest) -> Result<R, DataError>, + F2: FnMut(&mut R) -> &mut DataResponseMetadata, + { + let key_fallbacker = self.fallbacker.for_key(key); + let mut fallback_iterator = key_fallbacker.fallback_for(base_req.locale.clone()); + loop { + let result = f1(DataRequest { + locale: fallback_iterator.get(), + metadata: Default::default(), + }); + if !result_is_err_missing_data_options(&result) { + return result + .map(|mut res| { + f2(&mut res).locale = Some(fallback_iterator.take()); + res + }) + // Log the original request rather than the fallback request + .map_err(|e| e.with_req(key, base_req)); + } + // If we just checked und, break out of the loop. + if fallback_iterator.get().is_empty() { + break; + } + fallback_iterator.step(); + } + Err(DataErrorKind::MissingLocale.with_req(key, base_req)) + } +} + +impl<P> AnyProvider for LocaleFallbackProvider<P> +where + P: AnyProvider, +{ + fn load_any(&self, key: DataKey, base_req: DataRequest) -> Result<AnyResponse, DataError> { + self.run_fallback( + key, + base_req, + |req| self.inner.load_any(key, req), + |res| &mut res.metadata, + ) + } +} + +impl<P> BufferProvider for LocaleFallbackProvider<P> +where + P: BufferProvider, +{ + fn load_buffer( + &self, + key: DataKey, + base_req: DataRequest, + ) -> Result<DataResponse<BufferMarker>, DataError> { + self.run_fallback( + key, + base_req, + |req| self.inner.load_buffer(key, req), + |res| &mut res.metadata, + ) + } +} + +impl<P, M> DynamicDataProvider<M> for LocaleFallbackProvider<P> +where + P: DynamicDataProvider<M>, + M: DataMarker, +{ + fn load_data(&self, key: DataKey, base_req: DataRequest) -> Result<DataResponse<M>, DataError> { + self.run_fallback( + key, + base_req, + |req| self.inner.load_data(key, req), + |res| &mut res.metadata, + ) + } +} + +impl<P, M> DataProvider<M> for LocaleFallbackProvider<P> +where + P: DataProvider<M>, + M: KeyedDataMarker, +{ + fn load(&self, base_req: DataRequest) -> Result<DataResponse<M>, DataError> { + self.run_fallback( + M::KEY, + base_req, + |req| self.inner.load(req), + |res| &mut res.metadata, + ) + } +} diff --git a/vendor/icu_provider_adapters/src/fallback/algorithms.rs b/vendor/icu_provider_adapters/src/fallback/algorithms.rs new file mode 100644 index 000000000..9af52ef6d --- /dev/null +++ b/vendor/icu_provider_adapters/src/fallback/algorithms.rs @@ -0,0 +1,419 @@ +// 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 icu_locid::extensions::unicode::Key; +use icu_locid::extensions_unicode_key as key; +use icu_locid::subtags::Language; +use icu_locid::LanguageIdentifier; +use icu_provider::FallbackPriority; + +use super::*; + +const SUBDIVISION_KEY: Key = key!("sd"); + +impl<'a> LocaleFallbackerWithConfig<'a> { + pub(crate) fn normalize(&self, locale: &mut DataLocale) { + let language = locale.language(); + // 1. Populate the region (required for region fallback only) + if self.config.priority == FallbackPriority::Region && locale.region().is_none() { + // 1a. First look for region based on language+script + if let Some(script) = locale.script() { + locale.set_region( + self.likely_subtags + .ls2r + .get_2d(&language.into(), &script.into()) + .copied(), + ); + } + // 1b. If that fails, try language only + if locale.region().is_none() { + locale.set_region(self.likely_subtags.l2r.get(&language.into()).copied()); + } + } + // 2. Remove the script if it is implied by the other subtags + if let Some(script) = locale.script() { + let default_script = self + .likely_subtags + .l2s + .get_copied(&language.into()) + .unwrap_or(DEFAULT_SCRIPT); + if let Some(region) = locale.region() { + if script + == self + .likely_subtags + .lr2s + .get_copied_2d(&language.into(), ®ion.into()) + .unwrap_or(default_script) + { + locale.set_script(None); + } + } else if script == default_script { + locale.set_script(None); + } + } + // 3. Remove irrelevant extension subtags + locale.retain_unicode_ext(|key| { + match *key { + // Always retain -u-sd + SUBDIVISION_KEY => true, + // Retain the query-specific keyword + _ if Some(*key) == self.config.extension_key => true, + // Drop all others + _ => false, + } + }); + // 4. If there is an invalid "sd" subtag, drop it + // For now, ignore it, and let fallback do it for us + } +} + +impl<'a, 'b> LocaleFallbackIteratorInner<'a, 'b> { + pub fn step(&mut self, locale: &mut DataLocale) { + match self.config.priority { + FallbackPriority::Language => self.step_language(locale), + FallbackPriority::Region => self.step_region(locale), + // TODO(#1964): Change the collation fallback rules to be different + // from the language fallback fules. + FallbackPriority::Collation => self.step_language(locale), + // This case should not normally happen, but `FallbackPriority` is non_exhaustive. + // Make it go directly to `und`. + _ => { + debug_assert!( + false, + "Unknown FallbackPriority: {:?}", + self.config.priority + ); + *locale = Default::default() + } + } + } + + fn step_language(&mut self, locale: &mut DataLocale) { + // 1. Remove the extension fallback keyword + if let Some(extension_key) = self.config.extension_key { + if let Some(value) = locale.remove_unicode_ext(&extension_key) { + self.backup_extension = Some(value); + return; + } + } + // 2. Remove the subdivision keyword + if let Some(value) = locale.remove_unicode_ext(&SUBDIVISION_KEY) { + self.backup_subdivision = Some(value); + return; + } + // 3. Assert that the locale is a language identifier + debug_assert!(!locale.has_unicode_ext()); + // 4. Remove variants + if locale.has_variants() { + self.backup_variants = Some(locale.clear_variants()); + return; + } + // 5. Check for parent override + if let Some(parent) = self.get_explicit_parent(locale) { + locale.set_langid(parent); + self.restore_extensions_variants(locale); + return; + } + // 6. Add the script subtag if necessary + if locale.script().is_none() { + if let Some(region) = locale.region() { + let language = locale.language(); + if let Some(script) = self + .likely_subtags + .lr2s + .get_copied_2d(&language.into(), ®ion.into()) + { + locale.set_script(Some(script)); + self.restore_extensions_variants(locale); + return; + } + } + } + // 7. Remove region + if locale.region().is_some() { + locale.set_region(None); + return; + } + // 8. Remove language+script + debug_assert!(!locale.language().is_empty()); // don't call .step() on und + locale.set_script(None); + locale.set_language(Language::UND); + } + + fn step_region(&mut self, locale: &mut DataLocale) { + // 1. Remove the extension fallback keyword + if let Some(extension_key) = self.config.extension_key { + if let Some(value) = locale.remove_unicode_ext(&extension_key) { + self.backup_extension = Some(value); + return; + } + } + // 2. Remove the subdivision keyword + if let Some(value) = locale.remove_unicode_ext(&SUBDIVISION_KEY) { + self.backup_subdivision = Some(value); + return; + } + // 3. Assert that the locale is a language identifier + debug_assert!(!locale.has_unicode_ext()); + // 4. Remove variants + if locale.has_variants() { + self.backup_variants = Some(locale.clear_variants()); + return; + } + // 5. Remove language+script + if !locale.language().is_empty() || locale.script().is_some() { + locale.set_script(None); + locale.set_language(Language::UND); + self.restore_extensions_variants(locale); + return; + } + // 6. Remove region + debug_assert!(locale.region().is_some()); // don't call .step() on und + locale.set_region(None); + } + + fn restore_extensions_variants(&mut self, locale: &mut DataLocale) { + if let Some(value) = self.backup_extension.take() { + #[allow(clippy::unwrap_used)] // not reachable unless extension_key is present + locale.set_unicode_ext(self.config.extension_key.unwrap(), value); + } + if let Some(value) = self.backup_subdivision.take() { + locale.set_unicode_ext(SUBDIVISION_KEY, value); + } + if let Some(variants) = self.backup_variants.take() { + locale.set_variants(variants); + } + } + + fn get_explicit_parent(&self, locale: &DataLocale) -> Option<LanguageIdentifier> { + self.supplement + .and_then(|supplement| { + supplement + .parents + .get_copied_by(|uvstr| locale.strict_cmp(uvstr).reverse()) + }) + .or_else(|| { + self.parents + .parents + .get_copied_by(|uvstr| locale.strict_cmp(uvstr).reverse()) + }) + .map(LanguageIdentifier::from) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use icu_locid::Locale; + use std::str::FromStr; + + struct TestCase { + input: &'static str, + requires_data: bool, + extension_key: Option<Key>, + fallback_supplement: Option<FallbackSupplement>, + // Note: The first entry in the chain is the normalized locale + expected_language_chain: &'static [&'static str], + expected_region_chain: &'static [&'static str], + } + + // TODO: Consider loading these from a JSON file + const TEST_CASES: &[TestCase] = &[ + TestCase { + input: "en-u-hc-h12-sd-usca", + requires_data: false, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["en-u-sd-usca", "en"], + expected_region_chain: &["en-u-sd-usca", "en", "und-u-sd-usca"], + }, + TestCase { + input: "en-US-u-hc-h12-sd-usca", + requires_data: false, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["en-US-u-sd-usca", "en-US", "en"], + expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"], + }, + TestCase { + input: "en-US-fonipa-u-hc-h12-sd-usca", + requires_data: false, + extension_key: Some(key!("hc")), + fallback_supplement: None, + expected_language_chain: &[ + "en-US-fonipa-u-hc-h12-sd-usca", + "en-US-fonipa-u-sd-usca", + "en-US-fonipa", + "en-US", + "en", + ], + expected_region_chain: &[ + "en-US-fonipa-u-hc-h12-sd-usca", + "en-US-fonipa-u-sd-usca", + "en-US-fonipa", + "en-US", + "und-US-fonipa-u-hc-h12-sd-usca", + "und-US-fonipa-u-sd-usca", + "und-US-fonipa", + "und-US", + ], + }, + TestCase { + input: "en-u-hc-h12-sd-usca", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["en-u-sd-usca", "en"], + expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"], + }, + TestCase { + input: "en-Latn-u-sd-usca", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["en-u-sd-usca", "en"], + expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"], + }, + TestCase { + input: "en-Latn-US-u-sd-usca", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["en-US-u-sd-usca", "en-US", "en"], + expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"], + }, + TestCase { + // NOTE: -u-rg is not yet supported; when it is, this test should be updated + input: "en-u-rg-gbxxxx", + requires_data: false, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["en"], + expected_region_chain: &["en"], + }, + TestCase { + input: "sr-ME", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["sr-ME", "sr-Latn-ME", "sr-Latn"], + expected_region_chain: &["sr-ME", "und-ME"], + }, + TestCase { + input: "sr-ME-fonipa", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &[ + "sr-ME-fonipa", + "sr-ME", + "sr-Latn-ME-fonipa", + "sr-Latn-ME", + "sr-Latn", + ], + expected_region_chain: &["sr-ME-fonipa", "sr-ME", "und-ME-fonipa", "und-ME"], + }, + TestCase { + input: "de-Latn-LI", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["de-LI", "de"], + expected_region_chain: &["de-LI", "und-LI"], + }, + TestCase { + input: "ca-ES-valencia", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["ca-ES-valencia", "ca-ES", "ca"], + expected_region_chain: &["ca-ES-valencia", "ca-ES", "und-ES-valencia", "und-ES"], + }, + TestCase { + input: "es-AR", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["es-AR", "es-419", "es"], + expected_region_chain: &["es-AR", "und-AR"], + }, + TestCase { + input: "hi-IN", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["hi-IN", "hi"], + expected_region_chain: &["hi-IN", "und-IN"], + }, + TestCase { + input: "hi-Latn-IN", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["hi-Latn-IN", "hi-Latn", "en-IN", "en-001", "en"], + expected_region_chain: &["hi-Latn-IN", "und-IN"], + }, + TestCase { + input: "yue-HK", + requires_data: true, + extension_key: None, + fallback_supplement: None, + expected_language_chain: &["yue-HK", "yue"], + expected_region_chain: &["yue-HK", "und-HK"], + }, + TestCase { + input: "yue-HK", + requires_data: true, + extension_key: None, + fallback_supplement: Some(FallbackSupplement::Collation), + // TODO(#1964): add "zh" as a target. + expected_language_chain: &["yue-HK", "yue", "zh-Hant"], + expected_region_chain: &["yue-HK", "und-HK"], + }, + ]; + + #[test] + #[cfg(feature = "serde")] + fn test_fallback() { + let fallbacker_no_data = LocaleFallbacker::new_without_data(); + let fallbacker_with_data = + LocaleFallbacker::try_new_with_buffer_provider(&icu_testdata::buffer()).unwrap(); + for cas in TEST_CASES { + for (priority, expected_chain) in [ + (FallbackPriority::Language, cas.expected_language_chain), + (FallbackPriority::Region, cas.expected_region_chain), + ] { + let config = LocaleFallbackConfig { + priority, + extension_key: cas.extension_key, + fallback_supplement: cas.fallback_supplement, + }; + let key_fallbacker = if cas.requires_data { + fallbacker_with_data.for_config(config) + } else { + fallbacker_no_data.for_config(config) + }; + let locale = DataLocale::from(Locale::from_str(cas.input).unwrap()); + let mut it = key_fallbacker.fallback_for(locale); + for expected in expected_chain { + assert_eq!( + expected, + &it.get().to_string(), + "{:?} ({:?})", + cas.input, + priority + ); + it.step(); + } + assert_eq!( + "und", + it.get().to_string(), + "{:?} ({:?})", + cas.input, + priority + ); + } + } + } +} diff --git a/vendor/icu_provider_adapters/src/fallback/mod.rs b/vendor/icu_provider_adapters/src/fallback/mod.rs new file mode 100644 index 000000000..6ec636a09 --- /dev/null +++ b/vendor/icu_provider_adapters/src/fallback/mod.rs @@ -0,0 +1,390 @@ +// 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 ). + +//! Tools for locale fallback, enabling arbitrary input locales to be mapped into the nearest +//! locale with data. +//! +//! The algorithm implemented in this module is called [Flexible Vertical Fallback]( +//! https://docs.google.com/document/d/1Mp7EUyl-sFh_HZYgyeVwj88vJGpCBIWxzlCwGgLCDwM/edit). +//! Watch [#2243](https://github.com/unicode-org/icu4x/issues/2243) to track improvements to +//! this algorithm and steps to enshrine the algorithm in CLDR. +//! +//! # Examples +//! +//! Run the locale fallback algorithm: +//! +//! ``` +//! use icu_provider_adapters::fallback::LocaleFallbacker; +//! use icu_provider::prelude::*; +//! +//! // Set up a LocaleFallbacker with data. +//! let fallbacker = LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()).expect("data"); +//! +//! // Create a LocaleFallbackerWithConfig with a configuration for a specific key. +//! // By default, uses language priority with no additional extension keywords. +//! let key_fallbacker = fallbacker.for_config(Default::default()); +//! +//! // Set up the fallback iterator. +//! let mut fallback_iterator = key_fallbacker.fallback_for(icu_locid::locale!("hi-Latn-IN").into()); +//! +//! // Run the algorithm and check the results. +//! assert_eq!(fallback_iterator.get().to_string(), "hi-Latn-IN"); +//! fallback_iterator.step(); +//! assert_eq!(fallback_iterator.get().to_string(), "hi-Latn"); +//! fallback_iterator.step(); +//! assert_eq!(fallback_iterator.get().to_string(), "en-IN"); +//! fallback_iterator.step(); +//! assert_eq!(fallback_iterator.get().to_string(), "en-001"); +//! fallback_iterator.step(); +//! assert_eq!(fallback_iterator.get().to_string(), "en"); +//! fallback_iterator.step(); +//! assert_eq!(fallback_iterator.get().to_string(), "und"); +//! ``` + +use icu_locid::extensions::unicode::{Key, Value}; +use icu_locid::subtags::Variants; +use icu_provider::prelude::*; +use icu_provider::FallbackPriority; +use icu_provider::FallbackSupplement; + +mod adapter; +mod algorithms; +pub mod provider; + +pub use adapter::LocaleFallbackProvider; + +use provider::*; + +/// Configuration settings for a particular fallback operation. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[non_exhaustive] +pub struct LocaleFallbackConfig { + /// Strategy for choosing which subtags to drop during locale fallback. + /// + /// # Examples + /// + /// Retain the language and script subtags until the final step: + /// + /// ``` + /// use icu_provider::prelude::*; + /// use icu_provider::FallbackPriority; + /// use icu_provider_adapters::fallback::LocaleFallbackConfig; + /// use icu_provider_adapters::fallback::LocaleFallbacker; + /// + /// // Set up the fallback iterator. + /// let fallbacker = + /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()) + /// .expect("data"); + /// let mut config = LocaleFallbackConfig::default(); + /// config.priority = FallbackPriority::Language; + /// let key_fallbacker = fallbacker.for_config(config); + /// let mut fallback_iterator = key_fallbacker + /// .fallback_for(icu_locid::locale!("ca-ES-valencia").into()); + /// + /// // Run the algorithm and check the results. + /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES-valencia"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "ca"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und"); + /// ``` + /// + /// Retain the region subtag until the final step: + /// + /// ``` + /// use icu_provider::prelude::*; + /// use icu_provider::FallbackPriority; + /// use icu_provider_adapters::fallback::LocaleFallbackConfig; + /// use icu_provider_adapters::fallback::LocaleFallbacker; + /// + /// // Set up the fallback iterator. + /// let fallbacker = + /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()) + /// .expect("data"); + /// let mut config = LocaleFallbackConfig::default(); + /// config.priority = FallbackPriority::Region; + /// let key_fallbacker = fallbacker.for_config(config); + /// let mut fallback_iterator = key_fallbacker + /// .fallback_for(icu_locid::locale!("ca-ES-valencia").into()); + /// + /// // Run the algorithm and check the results. + /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES-valencia"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und-ES-valencia"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und-ES"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und"); + /// ``` + pub priority: FallbackPriority, + /// An extension keyword to retain during locale fallback. + /// + /// # Examples + /// + /// ``` + /// use icu_provider::prelude::*; + /// use icu_provider_adapters::fallback::LocaleFallbackConfig; + /// use icu_provider_adapters::fallback::LocaleFallbacker; + /// + /// // Set up the fallback iterator. + /// let fallbacker = + /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()) + /// .expect("data"); + /// let mut config = LocaleFallbackConfig::default(); + /// config.extension_key = Some(icu_locid::extensions_unicode_key!("nu")); + /// let key_fallbacker = fallbacker.for_config(config); + /// let mut fallback_iterator = key_fallbacker + /// .fallback_for(icu_locid::locale!("ar-EG-u-nu-latn").into()); + /// + /// // Run the algorithm and check the results. + /// assert_eq!(fallback_iterator.get().to_string(), "ar-EG-u-nu-latn"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "ar-EG"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "ar"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und"); + /// ``` + pub extension_key: Option<Key>, + /// Fallback supplement data key to customize fallback rules. + /// + /// For example, most data keys for collation add additional parent locales, such as + /// "yue" to "zh-Hant", and data used for the `"-u-co"` extension keyword fallback. + /// + /// Currently the only supported fallback supplement is `FallbackSupplement::Collation`, but more may be + /// added in the future. + /// + /// # Examples + /// + /// ``` + /// use icu_provider::prelude::*; + /// use icu_provider::FallbackPriority; + /// use icu_provider::FallbackSupplement; + /// use icu_provider_adapters::fallback::LocaleFallbackConfig; + /// use icu_provider_adapters::fallback::LocaleFallbacker; + /// use tinystr::tinystr; + /// + /// // Set up the fallback iterator. + /// let fallbacker = + /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()) + /// .expect("data"); + /// let mut config = LocaleFallbackConfig::default(); + /// config.priority = FallbackPriority::Collation; + /// config.fallback_supplement = Some(FallbackSupplement::Collation); + /// let key_fallbacker = fallbacker.for_config(config); + /// let mut fallback_iterator = + /// key_fallbacker.fallback_for(icu_locid::locale!("yue-HK").into()); + /// + /// // Run the algorithm and check the results. + /// // TODO(#1964): add "zh" as a target. + /// assert_eq!(fallback_iterator.get().to_string(), "yue-HK"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "yue"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "zh-Hant"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und"); + /// ``` + pub fallback_supplement: Option<FallbackSupplement>, +} + +/// Entry type for locale fallbacking. +/// +/// See the module-level documentation for an example. +#[derive(Debug, Clone, PartialEq)] +pub struct LocaleFallbacker { + likely_subtags: DataPayload<LocaleFallbackLikelySubtagsV1Marker>, + parents: DataPayload<LocaleFallbackParentsV1Marker>, + collation_supplement: Option<DataPayload<CollationFallbackSupplementV1Marker>>, +} + +/// Intermediate type for spawning locale fallback iterators based on a specific configuration. +/// +/// See the module-level documentation for an example. +#[derive(Debug, Clone, PartialEq)] +pub struct LocaleFallbackerWithConfig<'a> { + likely_subtags: &'a LocaleFallbackLikelySubtagsV1<'a>, + parents: &'a LocaleFallbackParentsV1<'a>, + supplement: Option<&'a LocaleFallbackSupplementV1<'a>>, + config: LocaleFallbackConfig, +} + +/// Inner iteration type. Does not own the item under fallback. +struct LocaleFallbackIteratorInner<'a, 'b> { + likely_subtags: &'a LocaleFallbackLikelySubtagsV1<'a>, + parents: &'a LocaleFallbackParentsV1<'a>, + supplement: Option<&'a LocaleFallbackSupplementV1<'a>>, + config: &'b LocaleFallbackConfig, + backup_extension: Option<Value>, + backup_subdivision: Option<Value>, + backup_variants: Option<Variants>, +} + +/// Iteration type for locale fallback operations. +/// +/// Because the `Iterator` trait does not allow items to borrow from the iterator, this class does +/// not implement that trait. Instead, use `.step()` and `.get()`. +pub struct LocaleFallbackIterator<'a, 'b> { + current: DataLocale, + inner: LocaleFallbackIteratorInner<'a, 'b>, +} + +impl LocaleFallbacker { + /// Creates a [`LocaleFallbacker`] with fallback data (likely subtags and parent locales). + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + /// <div class="stab unstable"> + /// ⚠️ The bounds on this function may change over time, including in SemVer minor releases. + /// </div> + pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError> + where + P: DataProvider<LocaleFallbackLikelySubtagsV1Marker> + + DataProvider<LocaleFallbackParentsV1Marker> + + DataProvider<CollationFallbackSupplementV1Marker> + + ?Sized, + { + let likely_subtags = provider.load(Default::default())?.take_payload()?; + let parents = provider.load(Default::default())?.take_payload()?; + let collation_supplement = match DataProvider::<CollationFallbackSupplementV1Marker>::load( + provider, + Default::default(), + ) { + Ok(response) => Some(response.take_payload()?), + // It is expected that not all keys are present + Err(DataError { + kind: DataErrorKind::MissingDataKey, + .. + }) => None, + Err(e) => return Err(e), + }; + Ok(LocaleFallbacker { + likely_subtags, + parents, + collation_supplement, + }) + } + + icu_provider::gen_any_buffer_constructors!(locale: skip, options: skip, error: DataError); + + /// Creates a [`LocaleFallbacker`] without fallback data. Using this constructor may result in + /// surprising behavior, especially in multi-script languages. + pub fn new_without_data() -> Self { + LocaleFallbacker { + likely_subtags: DataPayload::from_owned(Default::default()), + parents: DataPayload::from_owned(Default::default()), + collation_supplement: None, + } + } + + /// Creates the intermediate [`LocaleFallbackerWithConfig`] with configuration options. + pub fn for_config(&self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig { + let supplement = match config.fallback_supplement { + Some(FallbackSupplement::Collation) => { + self.collation_supplement.as_ref().map(|p| p.get()) + } + _ => None, + }; + LocaleFallbackerWithConfig { + likely_subtags: self.likely_subtags.get(), + parents: self.parents.get(), + supplement, + config, + } + } + + /// Creates the intermediate [`LocaleFallbackerWithConfig`] based on a + /// [`DataKey`] and a [`DataRequestMetadata`]. + /// + /// # Examples + /// + /// ``` + /// use icu_provider::prelude::*; + /// use icu_provider_adapters::fallback::LocaleFallbacker; + /// use std::borrow::Cow; + /// + /// // Define the data struct with key. + /// #[icu_provider::data_struct(marker( + /// FooV1Marker, + /// "demo/foo@1", + /// fallback_by = "region" + /// ))] + /// pub struct FooV1<'data> { + /// message: Cow<'data, str>, + /// }; + /// + /// // Set up the fallback iterator. + /// let fallbacker = + /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()) + /// .expect("data"); + /// let key_fallbacker = fallbacker.for_key(FooV1Marker::KEY); + /// let mut fallback_iterator = + /// key_fallbacker.fallback_for(icu_locid::locale!("en-GB").into()); + /// + /// // Run the algorithm and check the results. + /// assert_eq!(fallback_iterator.get().to_string(), "en-GB"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und-GB"); + /// fallback_iterator.step(); + /// assert_eq!(fallback_iterator.get().to_string(), "und"); + /// ``` + /// + /// [`DataRequestMetadata`]: icu_provider::DataRequestMetadata + pub fn for_key(&self, data_key: DataKey) -> LocaleFallbackerWithConfig { + let priority = data_key.metadata().fallback_priority; + let extension_key = data_key.metadata().extension_key; + let fallback_supplement = data_key.metadata().fallback_supplement; + self.for_config(LocaleFallbackConfig { + priority, + extension_key, + fallback_supplement, + }) + } +} + +impl<'a> LocaleFallbackerWithConfig<'a> { + /// Creates an iterator based on a [`DataLocale`] (which can be created from [`Locale`]). + /// + /// When first initialized, the locale is normalized according to the fallback algorithm. + /// + /// [`Locale`]: icu_locid::Locale + pub fn fallback_for<'b>(&'b self, mut locale: DataLocale) -> LocaleFallbackIterator<'a, 'b> { + self.normalize(&mut locale); + LocaleFallbackIterator { + current: locale, + inner: LocaleFallbackIteratorInner { + likely_subtags: self.likely_subtags, + parents: self.parents, + supplement: self.supplement, + config: &self.config, + backup_extension: None, + backup_subdivision: None, + backup_variants: None, + }, + } + } +} + +impl LocaleFallbackIterator<'_, '_> { + /// Borrows the current [`DataLocale`] under fallback. + pub fn get(&self) -> &DataLocale { + &self.current + } + + /// Takes the current [`DataLocale`] under fallback. + pub fn take(self) -> DataLocale { + self.current + } + + /// Performs one step of the locale fallback algorithm. + /// + /// The fallback is completed once the inner [`DataLocale`] becomes `und`. + pub fn step(&mut self) -> &mut Self { + self.inner.step(&mut self.current); + self + } +} diff --git a/vendor/icu_provider_adapters/src/fallback/provider.rs b/vendor/icu_provider_adapters/src/fallback/provider.rs new file mode 100644 index 000000000..edfa01f58 --- /dev/null +++ b/vendor/icu_provider_adapters/src/fallback/provider.rs @@ -0,0 +1,111 @@ +// 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 ). + +//! Data provider struct definitions for vertical fallback. +//! +//! Read more about data providers: [`icu_provider`] + +// Provider structs must be stable +#![allow(clippy::exhaustive_structs)] + +use icu_locid::extensions::unicode::Key; +use icu_locid::subtags::{Language, Region, Script}; +use icu_locid::{subtags_region as region, subtags_script as script}; +use tinystr::TinyAsciiStr; + +use icu_provider::prelude::*; + +use zerovec::ule::UnvalidatedStr; +use zerovec::ZeroMap; +use zerovec::ZeroMap2d; + +// We use raw TinyAsciiStrs for map keys, as we then don't have to +// validate them as subtags on deserialization. Map lookup can be +// done even if they are not valid tags (an invalid key will just +// become inaccessible). +type UnvalidatedLanguage = TinyAsciiStr<3>; +type UnvalidatedScript = TinyAsciiStr<4>; +type UnvalidatedRegion = TinyAsciiStr<3>; + +/// Locale fallback rules derived from likely subtags data. +#[icu_provider::data_struct(LocaleFallbackLikelySubtagsV1Marker = "fallback/likelysubtags@1")] +#[derive(Default, Clone, PartialEq, Debug)] +#[cfg_attr( + feature = "datagen", + derive(serde::Serialize, databake::Bake), + databake(path = icu_provider_adapters::fallback::provider), +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[yoke(prove_covariance_manually)] +pub struct LocaleFallbackLikelySubtagsV1<'data> { + /// Map from language to the default script in that language. Languages whose default script + /// is `Latn` are not included in the map for data size savings. + /// + /// Example: "zh" defaults to "Hans", which is in this map. + #[cfg_attr(feature = "serde", serde(borrow))] + pub l2s: ZeroMap<'data, UnvalidatedLanguage, Script>, + /// Map from language-region pairs to a script. Only populated if the script is different + /// from the one in `l2s` for that language. + /// + /// Example: "zh-TW" defaults to "Hant", which is in this map. + #[cfg_attr(feature = "serde", serde(borrow))] + pub lr2s: ZeroMap2d<'data, UnvalidatedLanguage, UnvalidatedRegion, Script>, + /// Map from language to the default region in that language. Languages whose default region + /// is `ZZ` are not included in the map for data size savings. + /// + /// Example: "zh" defaults to "CN". + #[cfg_attr(feature = "serde", serde(borrow))] + pub l2r: ZeroMap<'data, UnvalidatedLanguage, Region>, + /// Map from language-script pairs to a region. Only populated if the region is different + /// from the one in `l2r` for that language. + /// + /// Example: "zh-Hant" defaults to "TW". + #[cfg_attr(feature = "serde", serde(borrow))] + pub ls2r: ZeroMap2d<'data, UnvalidatedLanguage, UnvalidatedScript, Region>, +} + +/// `Latn` is the most common script, so it is defaulted for data size savings. +pub const DEFAULT_SCRIPT: Script = script!("Latn"); + +/// `ZZ` is the most common region, so it is defaulted for data size savings. +pub const DEFAULT_REGION: Region = region!("ZZ"); + +/// Locale fallback rules derived from CLDR parent locales data. +#[icu_provider::data_struct(LocaleFallbackParentsV1Marker = "fallback/parents@1")] +#[derive(Default, Clone, PartialEq, Debug)] +#[cfg_attr( + feature = "datagen", + derive(serde::Serialize, databake::Bake), + databake(path = icu_provider_adapters::fallback::provider), +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[yoke(prove_covariance_manually)] +pub struct LocaleFallbackParentsV1<'data> { + /// Map from language identifier to language identifier, indicating that the language on the + /// left should inherit from the language on the right. + #[cfg_attr(feature = "serde", serde(borrow))] + pub parents: ZeroMap<'data, UnvalidatedStr, (Language, Option<Script>, Option<Region>)>, +} + +/// Key-specific supplemental fallback data. +#[icu_provider::data_struct(marker( + CollationFallbackSupplementV1Marker, + "fallback/supplement/co@1" +))] +#[derive(Default, Clone, PartialEq, Debug)] +#[cfg_attr( + feature = "datagen", + derive(serde::Serialize, databake::Bake), + databake(path = icu_provider_adapters::fallback::provider), +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[yoke(prove_covariance_manually)] +pub struct LocaleFallbackSupplementV1<'data> { + /// Additional parent locales to supplement the common ones. + #[cfg_attr(feature = "serde", serde(borrow))] + pub parents: ZeroMap<'data, UnvalidatedStr, (Language, Option<Script>, Option<Region>)>, + /// Default values for Unicode extension keywords. + #[cfg_attr(feature = "serde", serde(borrow))] + pub unicode_extension_defaults: ZeroMap2d<'data, Key, UnvalidatedStr, UnvalidatedStr>, +} 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, + } + } +} diff --git a/vendor/icu_provider_adapters/src/fork/by_error.rs b/vendor/icu_provider_adapters/src/fork/by_error.rs new file mode 100644 index 000000000..5069229fa --- /dev/null +++ b/vendor/icu_provider_adapters/src/fork/by_error.rs @@ -0,0 +1,287 @@ +// 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::ForkByErrorPredicate; +use alloc::vec::Vec; +#[cfg(feature = "datagen")] +use icu_provider::datagen; +use icu_provider::prelude::*; + +/// A provider that returns data from one of two child providers based on a predicate function. +/// +/// This is an abstract forking provider that must be provided with a type implementing the +/// [`ForkByErrorPredicate`] trait. +/// +/// [`ForkByErrorProvider`] does not support forking between [`DataProvider`]s. However, it +/// supports forking between [`AnyProvider`], [`BufferProvider`], and [`DynamicDataProvider`]. +#[derive(Debug, PartialEq, Eq)] +pub struct ForkByErrorProvider<P0, P1, F>(P0, P1, F); + +impl<P0, P1, F> ForkByErrorProvider<P0, P1, F> { + /// Create a new provider that forks between the two children. + /// + /// The `predicate` argument should be an instance of a struct implementing + /// [`ForkByErrorPredicate`]. + pub fn new_with_predicate(p0: P0, p1: P1, predicate: F) -> Self { + Self(p0, p1, predicate) + } + + /// Returns references to the inner providers. + pub fn inner(&self) -> (&P0, &P1) { + (&self.0, &self.1) + } + + /// Returns ownership of the inner providers to the caller. + pub fn into_inner(self) -> (P0, P1) { + (self.0, self.1) + } +} + +impl<P0, P1, F> BufferProvider for ForkByErrorProvider<P0, P1, F> +where + P0: BufferProvider, + P1: BufferProvider, + F: ForkByErrorPredicate, +{ + fn load_buffer( + &self, + key: DataKey, + req: DataRequest, + ) -> Result<DataResponse<BufferMarker>, DataError> { + let result = self.0.load_buffer(key, req); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.2.test(key, Some(req), err) => return Err(err), + _ => (), + }; + self.1.load_buffer(key, req) + } +} + +impl<P0, P1, F> AnyProvider for ForkByErrorProvider<P0, P1, F> +where + P0: AnyProvider, + P1: AnyProvider, + F: ForkByErrorPredicate, +{ + fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> { + let result = self.0.load_any(key, req); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.2.test(key, Some(req), err) => return Err(err), + _ => (), + }; + self.1.load_any(key, req) + } +} + +impl<M, P0, P1, F> DynamicDataProvider<M> for ForkByErrorProvider<P0, P1, F> +where + M: DataMarker, + P0: DynamicDataProvider<M>, + P1: DynamicDataProvider<M>, + F: ForkByErrorPredicate, +{ + fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { + let result = self.0.load_data(key, req); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.2.test(key, Some(req), err) => return Err(err), + _ => (), + }; + self.1.load_data(key, req) + } +} + +#[cfg(feature = "datagen")] +impl<M, P0, P1, F> datagen::IterableDynamicDataProvider<M> for ForkByErrorProvider<P0, P1, F> +where + M: DataMarker, + P0: datagen::IterableDynamicDataProvider<M>, + P1: datagen::IterableDynamicDataProvider<M>, + F: ForkByErrorPredicate, +{ + fn supported_locales_for_key(&self, key: DataKey) -> Result<Vec<DataLocale>, DataError> { + let result = self.0.supported_locales_for_key(key); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.2.test(key, None, err) => return Err(err), + _ => (), + }; + self.1.supported_locales_for_key(key) + } +} + +/// A provider that returns data from the first child provider passing a predicate function. +/// +/// This is an abstract forking provider that must be provided with a type implementing the +/// [`ForkByErrorPredicate`] trait. +/// +/// [`MultiForkByErrorProvider`] does not support forking between [`DataProvider`]s. However, it +/// supports forking between [`AnyProvider`], [`BufferProvider`], and [`DynamicDataProvider`]. +pub struct MultiForkByErrorProvider<P, F> { + providers: Vec<P>, + predicate: F, +} + +impl<P, F> MultiForkByErrorProvider<P, F> { + /// Create a new provider that forks between the vector of children. + /// + /// The `predicate` argument should be an instance of a struct implementing + /// [`ForkByErrorPredicate`]. + pub fn new_with_predicate(providers: Vec<P>, predicate: F) -> Self { + Self { + providers, + predicate, + } + } + + /// Returns a slice of the inner providers. + pub fn inner(&self) -> &[P] { + &self.providers + } + + /// Returns ownership of the inner providers to the caller. + pub fn into_inner(self) -> Vec<P> { + self.providers + } +} + +impl<P, F> BufferProvider for MultiForkByErrorProvider<P, F> +where + P: BufferProvider, + F: ForkByErrorPredicate, +{ + fn load_buffer( + &self, + key: DataKey, + req: DataRequest, + ) -> Result<DataResponse<BufferMarker>, DataError> { + for provider in self.providers.iter() { + let result = provider.load_buffer(key, req); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.predicate.test(key, Some(req), err) => return Err(err), + _ => (), + }; + } + Err(DataErrorKind::MissingDataKey.with_key(key)) + } +} + +impl<P, F> AnyProvider for MultiForkByErrorProvider<P, F> +where + P: AnyProvider, + F: ForkByErrorPredicate, +{ + fn load_any(&self, key: DataKey, req: DataRequest) -> Result<AnyResponse, DataError> { + for provider in self.providers.iter() { + let result = provider.load_any(key, req); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.predicate.test(key, Some(req), err) => return Err(err), + _ => (), + }; + } + Err(DataErrorKind::MissingDataKey.with_key(key)) + } +} + +impl<M, P, F> DynamicDataProvider<M> for MultiForkByErrorProvider<P, F> +where + M: DataMarker, + P: DynamicDataProvider<M>, + F: ForkByErrorPredicate, +{ + fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { + for provider in self.providers.iter() { + let result = provider.load_data(key, req); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.predicate.test(key, Some(req), err) => return Err(err), + _ => (), + }; + } + Err(DataErrorKind::MissingDataKey.with_key(key)) + } +} + +#[cfg(feature = "datagen")] +impl<M, P, F> datagen::IterableDynamicDataProvider<M> for MultiForkByErrorProvider<P, F> +where + M: DataMarker, + P: datagen::IterableDynamicDataProvider<M>, + F: ForkByErrorPredicate, +{ + fn supported_locales_for_key(&self, key: DataKey) -> Result<Vec<DataLocale>, DataError> { + for provider in self.providers.iter() { + let result = provider.supported_locales_for_key(key); + match result { + Ok(ok) => return Ok(ok), + Err(err) if !self.predicate.test(key, None, err) => return Err(err), + _ => (), + }; + } + Err(DataErrorKind::MissingDataKey.with_key(key)) + } +} + +#[cfg(feature = "datagen")] +impl<P, MFrom, MTo, F> datagen::DataConverter<MFrom, MTo> for MultiForkByErrorProvider<P, F> +where + P: datagen::DataConverter<MFrom, MTo>, + F: ForkByErrorPredicate, + MFrom: DataMarker, + MTo: DataMarker, +{ + fn convert( + &self, + key: DataKey, + mut from: DataPayload<MFrom>, + ) -> Result<DataPayload<MTo>, (DataPayload<MFrom>, DataError)> { + for provider in self.providers.iter() { + let result = provider.convert(key, from); + match result { + Ok(ok) => return Ok(ok), + Err(e) => { + let (returned, err) = e; + if !self.predicate.test(key, None, err) { + return Err((returned, err)); + } + from = returned; + } + }; + } + Err((from, DataErrorKind::MissingDataKey.with_key(key))) + } +} + +#[cfg(feature = "datagen")] +impl<P0, P1, F, MFrom, MTo> datagen::DataConverter<MFrom, MTo> for ForkByErrorProvider<P0, P1, F> +where + P0: datagen::DataConverter<MFrom, MTo>, + P1: datagen::DataConverter<MFrom, MTo>, + F: ForkByErrorPredicate, + MFrom: DataMarker, + MTo: DataMarker, +{ + fn convert( + &self, + key: DataKey, + mut from: DataPayload<MFrom>, + ) -> Result<DataPayload<MTo>, (DataPayload<MFrom>, DataError)> { + let result = self.0.convert(key, from); + match result { + Ok(ok) => return Ok(ok), + Err(e) => { + let (returned, err) = e; + if !self.2.test(key, None, err) { + return Err((returned, err)); + } + from = returned; + } + }; + self.1.convert(key, from) + } +} diff --git a/vendor/icu_provider_adapters/src/fork/macros.rs b/vendor/icu_provider_adapters/src/fork/macros.rs new file mode 100644 index 000000000..7c18a9ba3 --- /dev/null +++ b/vendor/icu_provider_adapters/src/fork/macros.rs @@ -0,0 +1,71 @@ +// 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 ). + +/// Make a forking data provider with an arbitrary number of inner providers +/// that are known at build time. +/// +/// # Examples +/// +/// ``` +/// use icu_provider_adapters::fork::ForkByKeyProvider; +/// +/// // Some empty example providers: +/// #[derive(Default, PartialEq, Debug)] +/// struct Provider1; +/// #[derive(Default, PartialEq, Debug)] +/// struct Provider2; +/// #[derive(Default, PartialEq, Debug)] +/// struct Provider3; +/// +/// // Combine them into one: +/// let forking1 = icu_provider_adapters::make_forking_provider!( +/// ForkByKeyProvider::new, +/// [ +/// Provider1::default(), +/// Provider2::default(), +/// Provider3::default(), +/// ] +/// ); +/// +/// // This is equivalent to: +/// let forking2 = ForkByKeyProvider::new( +/// Provider1::default(), +/// ForkByKeyProvider::new(Provider2::default(), Provider3::default()), +/// ); +/// +/// assert_eq!(forking1, forking2); +/// ``` +#[macro_export] +macro_rules! make_forking_provider { + // Base case: + ($combo_p:path, [ $a:expr, $b:expr, ]) => { + $combo_p($a, $b) + }; + // General list: + ($combo_p:path, [ $a:expr, $b:expr, $($c:expr),+, ]) => { + $combo_p($a, $crate::make_forking_provider!($combo_p, [ $b, $($c),+, ])) + }; +} + +#[cfg(test)] +mod test { + #[derive(Default)] + struct Provider1; + #[derive(Default)] + struct Provider2; + #[derive(Default)] + struct Provider3; + + #[test] + fn test_make_forking_provider() { + make_forking_provider!( + crate::fork::ForkByKeyProvider::new, + [ + Provider1::default(), + Provider2::default(), + Provider3::default(), + ] + ); + } +} diff --git a/vendor/icu_provider_adapters/src/fork/mod.rs b/vendor/icu_provider_adapters/src/fork/mod.rs new file mode 100644 index 000000000..05ba5fd19 --- /dev/null +++ b/vendor/icu_provider_adapters/src/fork/mod.rs @@ -0,0 +1,226 @@ +// 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 combine multiple other providers. +//! +//! # Types of Forking Providers +//! +//! ## Key-Based +//! +//! To fork between providers that support different data keys, see: +//! +//! - [`ForkByKeyProvider`] +//! - [`MultiForkByKeyProvider`] +//! +//! ## Locale-Based +//! +//! To fork between providers that support different locales, see: +//! +//! - [`ForkByErrorProvider`]`<`[`MissingLocalePredicate`]`>` +//! - [`MultiForkByErrorProvider`]`<`[`MissingLocalePredicate`]`>` +//! +//! [`MissingLocalePredicate`]: predicates::MissingLocalePredicate +//! +//! # Examples +//! +//! See: +//! +//! - [`ForkByKeyProvider`] +//! - [`MultiForkByKeyProvider`] +//! - [`MissingLocalePredicate`] + +use alloc::vec::Vec; + +mod by_error; + +pub mod predicates; + +#[macro_use] +mod macros; + +pub use by_error::ForkByErrorProvider; +pub use by_error::MultiForkByErrorProvider; + +use predicates::ForkByErrorPredicate; +use predicates::MissingDataKeyPredicate; + +/// Create a provider that returns data from one of two child providers based on the key. +/// +/// The result of the first provider that supports a particular [`DataKey`] will be returned, +/// even if the request failed for other reasons (such as an unsupported language). Therefore, +/// you should add child providers that support disjoint sets of keys. +/// +/// [`ForkByKeyProvider`] does not support forking between [`DataProvider`]s. However, it +/// supports forking between [`AnyProvider`], [`BufferProvider`], and [`DynamicDataProvider`]. +/// +/// # Examples +/// +/// Normal usage: +/// +/// ``` +/// use icu_locid::locale; +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// use icu_provider_adapters::fork::ForkByKeyProvider; +/// +/// struct DummyBufferProvider; +/// impl BufferProvider for DummyBufferProvider { +/// fn load_buffer( +/// &self, +/// key: DataKey, +/// req: DataRequest, +/// ) -> Result<DataResponse<BufferMarker>, DataError> { +/// Err(DataErrorKind::MissingDataKey.with_req(key, req)) +/// } +/// } +/// +/// let forking_provider = ForkByKeyProvider::new( +/// DummyBufferProvider, +/// HelloWorldProvider.into_json_provider(), +/// ); +/// +/// let data_provider = forking_provider.as_deserializing(); +/// +/// let german_hello_world: DataPayload<HelloWorldV1Marker> = data_provider +/// .load(DataRequest { +/// locale: &locale!("de").into(), +/// metadata: Default::default(), +/// }) +/// .expect("Loading should succeed") +/// .take_payload() +/// .expect("Data should be present"); +/// +/// assert_eq!("Hallo Welt", german_hello_world.get().message); +/// ``` +/// +/// Stops at the first provider supporting a key, even if the locale is not supported: +/// +/// ``` +/// use icu_locid::{subtags_language as language, locale}; +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// use icu_provider_adapters::filter::Filterable; +/// use icu_provider_adapters::fork::ForkByKeyProvider; +/// +/// let forking_provider = ForkByKeyProvider::new( +/// HelloWorldProvider +/// .into_json_provider() +/// .filterable("Chinese") +/// .filter_by_langid(|langid| langid.language == language!("zh")), +/// HelloWorldProvider +/// .into_json_provider() +/// .filterable("German") +/// .filter_by_langid(|langid| langid.language == language!("de")), +/// ); +/// +/// let data_provider: &dyn DataProvider<HelloWorldV1Marker> = +/// &forking_provider.as_deserializing(); +/// +/// // Chinese is the first provider, so this succeeds +/// let chinese_hello_world = data_provider +/// .load(DataRequest { +/// locale: &locale!("zh").into(), +/// metadata: Default::default(), +/// }) +/// .expect("Loading should succeed") +/// .take_payload() +/// .expect("Data should be present"); +/// +/// assert_eq!("你好世界", chinese_hello_world.get().message); +/// +/// // German is shadowed by Chinese, so this fails +/// data_provider +/// .load(DataRequest { +/// locale: &locale!("de").into(), +/// metadata: Default::default(), +/// }) +/// .expect_err("Should stop at the first provider, even though the second has data"); +/// ``` +/// +/// [`DataKey`]: icu_provider::DataKey +/// [`DataProvider`]: icu_provider::DataProvider +/// [`AnyProvider`]: icu_provider::AnyProvider +/// [`BufferProvider`]: icu_provider::BufferProvider +/// [`DynamicDataProvider`]: icu_provider::DynamicDataProvider +pub type ForkByKeyProvider<P0, P1> = ForkByErrorProvider<P0, P1, MissingDataKeyPredicate>; + +impl<P0, P1> ForkByKeyProvider<P0, P1> { + /// A provider that returns data from one of two child providers based on the key. + /// + /// See [`ForkByKeyProvider`]. + pub fn new(p0: P0, p1: P1) -> Self { + ForkByErrorProvider::new_with_predicate(p0, p1, MissingDataKeyPredicate) + } +} + +/// A provider that returns data from the first child provider supporting the key. +/// +/// The result of the first provider that supports a particular [`DataKey`] will be returned, +/// even if the request failed for other reasons (such as an unsupported language). Therefore, +/// you should add child providers that support disjoint sets of keys. +/// +/// [`MultiForkByKeyProvider`] does not support forking between [`DataProvider`]s. However, it +/// supports forking between [`AnyProvider`], [`BufferProvider`], and [`DynamicDataProvider`]. +/// +/// # Examples +/// +/// ``` +/// use icu_locid::{subtags_language as language, locale}; +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// use icu_provider_adapters::filter::Filterable; +/// use icu_provider_adapters::fork::MultiForkByKeyProvider; +/// +/// let forking_provider = MultiForkByKeyProvider::new( +/// vec![ +/// HelloWorldProvider +/// .into_json_provider() +/// .filterable("Chinese") +/// .filter_by_langid(|langid| langid.language == language!("zh")), +/// HelloWorldProvider +/// .into_json_provider() +/// .filterable("German") +/// .filter_by_langid(|langid| langid.language == language!("de")), +/// ], +/// ); +/// +/// let data_provider: &dyn DataProvider<HelloWorldV1Marker> = +/// &forking_provider.as_deserializing(); +/// +/// // Chinese is the first provider, so this succeeds +/// let chinese_hello_world = data_provider +/// .load(DataRequest { +/// locale: &locale!("zh").into(), +/// metadata: Default::default(), +/// }) +/// .expect("Loading should succeed") +/// .take_payload() +/// .expect("Data should be present"); +/// +/// assert_eq!("你好世界", chinese_hello_world.get().message); +/// +/// // German is shadowed by Chinese, so this fails +/// data_provider +/// .load(DataRequest { +/// locale: &locale!("de").into(), +/// metadata: Default::default(), +/// }) +/// .expect_err("Should stop at the first provider, even though the second has data"); +/// ``` +/// +/// [`DataKey`]: icu_provider::DataKey +/// [`DataProvider`]: icu_provider::DataProvider +/// [`AnyProvider`]: icu_provider::AnyProvider +/// [`BufferProvider`]: icu_provider::BufferProvider +/// [`DynamicDataProvider`]: icu_provider::DynamicDataProvider +pub type MultiForkByKeyProvider<P> = MultiForkByErrorProvider<P, MissingDataKeyPredicate>; + +impl<P> MultiForkByKeyProvider<P> { + /// Create a provider that returns data from the first child provider supporting the key. + /// + /// See [`MultiForkByKeyProvider`]. + pub fn new(providers: Vec<P>) -> Self { + MultiForkByErrorProvider::new_with_predicate(providers, MissingDataKeyPredicate) + } +} diff --git a/vendor/icu_provider_adapters/src/fork/predicates.rs b/vendor/icu_provider_adapters/src/fork/predicates.rs new file mode 100644 index 000000000..0fefe5704 --- /dev/null +++ b/vendor/icu_provider_adapters/src/fork/predicates.rs @@ -0,0 +1,138 @@ +// 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 ). + +//! Collection of predicate traits and functions for forking providers. + +use icu_provider::prelude::*; + +/// The predicate trait used by [`ForkByErrorProvider`]. +/// +/// [`ForkByErrorProvider`]: super::ForkByErrorProvider +pub trait ForkByErrorPredicate { + /// This function is called when a data request fails and there are additional providers + /// that could possibly fulfill the request. + /// + /// Arguments: + /// + /// - `&self` = Reference to the struct implementing the trait (for data capture) + /// - `key` = The [`DataKey`] associated with the request + /// - `req` = The [`DataRequest`]. This may be `None` if there is no request, such as + /// inside [`IterableDynamicDataProvider`]. + /// - `err` = The error that occurred. + /// + /// Return value: + /// + /// - `true` to discard the error and attempt the request with the next provider. + /// - `false` to return the error and not perform any additional requests. + /// + /// [`DataKey`]: icu_provider::DataKey + /// [`DataRequest`]: icu_provider::DataRequest + /// [`IterableDynamicDataProvider`]: icu_provider::datagen::IterableDynamicDataProvider + fn test(&self, key: DataKey, req: Option<DataRequest>, err: DataError) -> bool; +} + +/// A predicate that allows forking providers to search for a provider that supports a +/// particular data key. +/// +/// This is normally used implicitly by [`ForkByKeyProvider`]. +/// +/// [`ForkByKeyProvider`]: super::ForkByKeyProvider +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] // Not intended to be constructed +pub struct MissingDataKeyPredicate; + +impl ForkByErrorPredicate for MissingDataKeyPredicate { + #[inline] + fn test(&self, _: DataKey, _: Option<DataRequest>, err: DataError) -> bool { + matches!( + err, + DataError { + kind: DataErrorKind::MissingDataKey, + .. + } + ) + } +} + +/// A predicate that allows forking providers to search for a provider that supports a +/// particular locale, based on whether it returns [`DataErrorKind::MissingLocale`]. +/// +/// # Examples +/// +/// Configure a multi-language data provider pointing at two language packs: +/// +/// ``` +/// use icu_provider_adapters::fork::ForkByErrorProvider; +/// use icu_provider_adapters::fork::predicates::MissingLocalePredicate; +/// use icu_provider_fs::FsDataProvider; +/// use icu_provider::prelude::*; +/// use icu_provider::hello_world::HelloWorldV1Marker; +/// use icu_locid::locale; +/// +/// // The `tests` directory contains two separate "language packs" for Hello World data. +/// let base_dir = std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")) +/// .join("tests/data/langtest"); +/// let provider_de = FsDataProvider::try_new(base_dir.join("de")).unwrap(); +/// let provider_ro = FsDataProvider::try_new(base_dir.join("ro")).unwrap(); +/// +/// // Create the forking provider: +/// let provider = ForkByErrorProvider::new_with_predicate( +/// provider_de, +/// provider_ro, +/// MissingLocalePredicate +/// ); +/// +/// // Test that we can load both "de" and "ro" data: +/// +/// let german_hello_world: DataPayload<HelloWorldV1Marker> = provider +/// .as_deserializing() +/// .load(DataRequest { +/// locale: &locale!("de").into(), +/// metadata: Default::default(), +/// }) +/// .expect("Loading should succeed") +/// .take_payload() +/// .expect("Data should be present"); +/// +/// assert_eq!("Hallo Welt", german_hello_world.get().message); +/// +/// let romanian_hello_world: DataPayload<HelloWorldV1Marker> = provider +/// .as_deserializing() +/// .load(DataRequest { +/// locale: &locale!("ro").into(), +/// metadata: Default::default(), +/// }) +/// .expect("Loading should succeed") +/// .take_payload() +/// .expect("Data should be present"); +/// +/// assert_eq!("Salut, lume", romanian_hello_world.get().message); +/// +/// // We should not be able to load "en" data because it is not in the provider: +/// +/// DataProvider::<HelloWorldV1Marker>::load( +/// &provider.as_deserializing(), +/// DataRequest { +/// locale: &locale!("en").into(), +/// metadata: Default::default(), +/// } +/// ) +/// .expect_err("No English data"); +/// ``` +#[derive(Debug, PartialEq, Eq)] +#[allow(clippy::exhaustive_structs)] // empty type +pub struct MissingLocalePredicate; + +impl ForkByErrorPredicate for MissingLocalePredicate { + #[inline] + fn test(&self, _: DataKey, _: Option<DataRequest>, err: DataError) -> bool { + matches!( + err, + DataError { + kind: DataErrorKind::MissingLocale, + .. + } + ) + } +} diff --git a/vendor/icu_provider_adapters/src/helpers.rs b/vendor/icu_provider_adapters/src/helpers.rs new file mode 100644 index 000000000..2b168f936 --- /dev/null +++ b/vendor/icu_provider_adapters/src/helpers.rs @@ -0,0 +1,15 @@ +// 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 icu_provider::prelude::*; + +pub(crate) fn result_is_err_missing_data_options<T>(result: &Result<T, DataError>) -> bool { + matches!( + result, + Err(DataError { + kind: DataErrorKind::MissingLocale, + .. + }) + ) +} diff --git a/vendor/icu_provider_adapters/src/lib.rs b/vendor/icu_provider_adapters/src/lib.rs new file mode 100644 index 000000000..16361e891 --- /dev/null +++ b/vendor/icu_provider_adapters/src/lib.rs @@ -0,0 +1,36 @@ +// 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 ). + +//! Adapters for composing and manipulating data providers. +//! +//! - Use the [`fork`] module to marshall data requests between multiple possible providers. +//! - Use the [`either`] module to choose between multiple provider types at runtime. +//! - Use the [`filter`] module to programmatically reject certain data requests. +//! - Use the [`fallback`] module to automatically resolve arbitrary locales for data loading. + +// https://github.com/unicode-org/icu4x/blob/main/docs/process/boilerplate.md#library-annotations +#![cfg_attr(not(any(test, feature = "std")), no_std)] +#![cfg_attr( + not(test), + deny( + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, + clippy::exhaustive_structs, + clippy::exhaustive_enums, + // TODO(#2266): enable missing_debug_implementations, + ) +)] +#![warn(missing_docs)] + +extern crate alloc; + +pub mod any_payload; +pub mod either; +pub mod empty; +pub mod fallback; +pub mod filter; +pub mod fork; +mod helpers; diff --git a/vendor/icu_provider_adapters/tests/data/langtest/de/core/helloworld@1/de.json b/vendor/icu_provider_adapters/tests/data/langtest/de/core/helloworld@1/de.json new file mode 100644 index 000000000..82d456e8e --- /dev/null +++ b/vendor/icu_provider_adapters/tests/data/langtest/de/core/helloworld@1/de.json @@ -0,0 +1 @@ +{"message":"Hallo Welt"} diff --git a/vendor/icu_provider_adapters/tests/data/langtest/de/manifest.json b/vendor/icu_provider_adapters/tests/data/langtest/de/manifest.json new file mode 100644 index 000000000..847813772 --- /dev/null +++ b/vendor/icu_provider_adapters/tests/data/langtest/de/manifest.json @@ -0,0 +1,3 @@ +{ + "syntax": "Json" +} diff --git a/vendor/icu_provider_adapters/tests/data/langtest/ro/core/helloworld@1/ro.json b/vendor/icu_provider_adapters/tests/data/langtest/ro/core/helloworld@1/ro.json new file mode 100644 index 000000000..a9b3e8dcf --- /dev/null +++ b/vendor/icu_provider_adapters/tests/data/langtest/ro/core/helloworld@1/ro.json @@ -0,0 +1 @@ +{"message":"Salut, lume"} diff --git a/vendor/icu_provider_adapters/tests/data/langtest/ro/manifest.json b/vendor/icu_provider_adapters/tests/data/langtest/ro/manifest.json new file mode 100644 index 000000000..847813772 --- /dev/null +++ b/vendor/icu_provider_adapters/tests/data/langtest/ro/manifest.json @@ -0,0 +1,3 @@ +{ + "syntax": "Json" +} |