Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl ExportableProvider for ForkByErrorProvider and add tutorial #5503

Merged
merged 9 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions provider/adapters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ serde = { workspace = true, features = ["derive", "alloc"], optional = true }
icu_provider = { path = "../../provider/core", features = ["macros", "deserialize_json"] }
icu_locale = { path = "../../components/locale" }
writeable = { path = "../../utils/writeable" }

[features]
std = []
export = ["icu_provider/export", "std"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems incomplete to not implement ExportableProvider on all adapter types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ForkByMarkerProvider and MultiForkByMarkerProvider are still missing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub type ForkByMarkerProvider<P0, P1> = ForkByErrorProvider<P0, P1, MarkerNotFoundPredicate>;

17 changes: 17 additions & 0 deletions provider/adapters/src/either.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
//! Helpers for switching between multiple providers.

use alloc::collections::BTreeSet;
#[cfg(feature = "export")]
use icu_provider::export::ExportableProvider;
use icu_provider::prelude::*;

/// A provider that is one of two types determined at runtime.
Expand Down Expand Up @@ -122,3 +124,18 @@ impl<M: DataMarker, P0: IterableDataProvider<M>, P1: IterableDataProvider<M>>
}
}
}

#[cfg(feature = "export")]
impl<P0, P1> ExportableProvider for EitherProvider<P0, P1>
where
P0: ExportableProvider,
P1: ExportableProvider,
{
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
use EitherProvider::*;
match self {
A(p) => p.supported_markers(),
B(p) => p.supported_markers(),
}
}
}
9 changes: 9 additions & 0 deletions provider/adapters/src/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! Use [`EmptyDataProvider`] as a stand-in for a provider that always fails.

use alloc::collections::BTreeSet;
#[cfg(feature = "export")]
use icu_provider::export::ExportableProvider;
use icu_provider::prelude::*;

/// A data provider that always returns an error.
Expand Down Expand Up @@ -107,3 +109,10 @@ where
Ok(Default::default())
}
}

#[cfg(feature = "export")]
impl ExportableProvider for EmptyDataProvider {
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
Default::default()
}
}
14 changes: 14 additions & 0 deletions provider/adapters/src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
mod impls;

use alloc::collections::BTreeSet;
#[cfg(feature = "export")]
use icu_provider::export::ExportableProvider;
use icu_provider::prelude::*;

/// A data provider that selectively filters out data requests.
Expand Down Expand Up @@ -165,3 +167,15 @@ where
})
}
}

#[cfg(feature = "export")]
impl<P0, F> ExportableProvider for FilterDataProvider<P0, F>
where
P0: ExportableProvider,
F: Fn(DataIdentifierBorrowed) -> bool + Sync,
{
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
// The predicate only takes DataIdentifier, not DataMarker, so we are not impacted
self.inner.supported_markers()
}
}
30 changes: 30 additions & 0 deletions provider/adapters/src/fork/by_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use super::ForkByErrorPredicate;
use alloc::{collections::BTreeSet, vec::Vec};
#[cfg(feature = "export")]
use icu_provider::export::ExportableProvider;
use icu_provider::prelude::*;

/// A provider that returns data from one of two child providers based on a predicate function.
Expand Down Expand Up @@ -159,6 +161,20 @@ where
}
}

#[cfg(feature = "export")]
impl<P0, P1, F> ExportableProvider for ForkByErrorProvider<P0, P1, F>
where
P0: ExportableProvider,
P1: ExportableProvider,
F: ForkByErrorPredicate + Sync,
{
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
let mut markers = self.0.supported_markers();
markers.extend(self.1.supported_markers());
markers
Comment on lines +172 to +174
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut markers = self.0.supported_markers();
markers.extend(self.1.supported_markers());
markers
[&self.0, &self.1].iter().map(|p| p.supported_markers()).collect()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure

}
}

/// 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
Expand Down Expand Up @@ -334,3 +350,17 @@ where
Err(last_error)
}
}

#[cfg(feature = "export")]
impl<P, F> ExportableProvider for MultiForkByErrorProvider<P, F>
where
P: ExportableProvider,
F: ForkByErrorPredicate + Sync,
{
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
self.providers
.iter()
.flat_map(|p| p.supported_markers())
.collect()
}
}
2 changes: 1 addition & 1 deletion provider/adapters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! - Use the [`fallback`] module to automatically resolve arbitrary locales for data loading.

// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![cfg_attr(
not(test),
deny(
Expand Down
5 changes: 3 additions & 2 deletions tools/md-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ edition = "2021"

[dev-dependencies]
icu = { workspace = true, features = ["compiled_data", "serde"] }
icu_provider_export = { workspace = true }
icu_provider_export = { workspace = true, features = ["blob_exporter"] }
icu_provider_source = { workspace = true, features = ["networking"] }
icu_provider = { workspace = true, features = ["deserialize_json"] }
icu_provider_adapters = { workspace = true, features = ["serde"] }
icu_provider_adapters = { workspace = true, features = ["serde", "export"] }
icu_provider_blob = { workspace = true }
icu_provider_fs = { workspace = true }

Expand Down
77 changes: 77 additions & 0 deletions tutorials/data_provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,83 @@ assert_eq!(formatter.format_to_string(&100007i64.into()), "100🐮007");

Forking providers can be implemented using `DataPayload::dynamic_cast`. For an example, see that function's documentation.

## Exporting Custom Data Markers

To add custom data markers to your baked data or postcard file, create a forking exportable provider:

```rust
use icu::locale::locale;
use icu::plurals::provider::CardinalV1Marker;
use icu_provider::prelude::*;
use icu_provider::DataMarker;
use icu_provider_adapters::fork::ForkByMarkerProvider;
use icu_provider_blob::BlobDataProvider;
use icu_provider_export::blob_exporter::BlobExporter;
use icu_provider_export::prelude::*;
use icu_provider_source::SourceDataProvider;
use std::borrow::Cow;
use std::collections::BTreeSet;

#[icu_provider::data_struct(marker(CustomMarker, "x/custom@1"))]
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize, databake::Bake)]
#[databake(path = crate)]
pub struct Custom<'data> {
message: Cow<'data, str>,
};

struct CustomProvider;
impl DataProvider<CustomMarker> for CustomProvider {
fn load(&self, req: DataRequest) -> Result<DataResponse<CustomMarker>, DataError> {
Ok(DataResponse {
metadata: Default::default(),
payload: DataPayload::from_owned(Custom {
message: format!("Custom data for locale {}!", req.id.locale).into(),
}),
})
}
}

impl IterableDataProvider<CustomMarker> for CustomProvider {
fn iter_ids(&self) -> Result<BTreeSet<DataIdentifierCow>, DataError> {
Ok([locale!("es"), locale!("ja")]
.into_iter()
.map(DataLocale::from)
.map(DataIdentifierCow::from_locale)
.collect())
}
}

icu_provider::export::make_exportable_provider!(CustomProvider, [CustomMarker,]);

let icu4x_source_provider = SourceDataProvider::new_latest_tested();
let custom_source_provider = CustomProvider;

let mut buffer = Vec::<u8>::new();

ExportDriver::new(
[DataLocaleFamily::FULL],
DeduplicationStrategy::None.into(),
LocaleFallbacker::try_new_unstable(&icu4x_source_provider).unwrap(),
)
.with_markers([CardinalV1Marker::INFO, CustomMarker::INFO])
.export(
&ForkByMarkerProvider::new(icu4x_source_provider, custom_source_provider),
BlobExporter::new_v2_with_sink(Box::new(&mut buffer)),
)
.unwrap();

let blob_provider = BlobDataProvider::try_new_from_blob(buffer.into()).unwrap();

let locale = DataLocale::from(&locale!("ja"));
let req = DataRequest {
id: DataIdentifierBorrowed::for_locale(&locale),
metadata: Default::default(),
};

assert!(blob_provider.load_data(CardinalV1Marker::INFO, req).is_ok());
assert!(blob_provider.load_data(CustomMarker::INFO, req).is_ok());
```

## Accessing the Resolved Locale

ICU4X objects do not store their "resolved locale" because that is not a well-defined concept. Components can load data from many sources, and fallbacks to parent locales or root does not necessarily mean that a locale is not supported.
Expand Down