Skip to content

Commit 3694bfe

Browse files
authored
impl ExportableProvider for ForkByErrorProvider and add tutorial (#5503)
1 parent cd7075d commit 3694bfe

File tree

9 files changed

+156
-3
lines changed

9 files changed

+156
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

provider/adapters/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ serde = { workspace = true, features = ["derive", "alloc"], optional = true }
2929
icu_provider = { path = "../../provider/core", features = ["macros", "deserialize_json"] }
3030
icu_locale = { path = "../../components/locale" }
3131
writeable = { path = "../../utils/writeable" }
32+
33+
[features]
34+
std = []
35+
export = ["icu_provider/export", "std"]

provider/adapters/src/either.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
//! Helpers for switching between multiple providers.
66
77
use alloc::collections::BTreeSet;
8+
#[cfg(feature = "export")]
9+
use icu_provider::export::ExportableProvider;
810
use icu_provider::prelude::*;
911

1012
/// A provider that is one of two types determined at runtime.
@@ -122,3 +124,18 @@ impl<M: DataMarker, P0: IterableDataProvider<M>, P1: IterableDataProvider<M>>
122124
}
123125
}
124126
}
127+
128+
#[cfg(feature = "export")]
129+
impl<P0, P1> ExportableProvider for EitherProvider<P0, P1>
130+
where
131+
P0: ExportableProvider,
132+
P1: ExportableProvider,
133+
{
134+
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
135+
use EitherProvider::*;
136+
match self {
137+
A(p) => p.supported_markers(),
138+
B(p) => p.supported_markers(),
139+
}
140+
}
141+
}

provider/adapters/src/empty.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
//! Use [`EmptyDataProvider`] as a stand-in for a provider that always fails.
88
99
use alloc::collections::BTreeSet;
10+
#[cfg(feature = "export")]
11+
use icu_provider::export::ExportableProvider;
1012
use icu_provider::prelude::*;
1113

1214
/// A data provider that always returns an error.
@@ -107,3 +109,10 @@ where
107109
Ok(Default::default())
108110
}
109111
}
112+
113+
#[cfg(feature = "export")]
114+
impl ExportableProvider for EmptyDataProvider {
115+
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
116+
Default::default()
117+
}
118+
}

provider/adapters/src/filter/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
mod impls;
2424

2525
use alloc::collections::BTreeSet;
26+
#[cfg(feature = "export")]
27+
use icu_provider::export::ExportableProvider;
2628
use icu_provider::prelude::*;
2729

2830
/// A data provider that selectively filters out data requests.
@@ -165,3 +167,15 @@ where
165167
})
166168
}
167169
}
170+
171+
#[cfg(feature = "export")]
172+
impl<P0, F> ExportableProvider for FilterDataProvider<P0, F>
173+
where
174+
P0: ExportableProvider,
175+
F: Fn(DataIdentifierBorrowed) -> bool + Sync,
176+
{
177+
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
178+
// The predicate only takes DataIdentifier, not DataMarker, so we are not impacted
179+
self.inner.supported_markers()
180+
}
181+
}

provider/adapters/src/fork/by_error.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use super::ForkByErrorPredicate;
66
use alloc::{collections::BTreeSet, vec::Vec};
7+
#[cfg(feature = "export")]
8+
use icu_provider::export::ExportableProvider;
79
use icu_provider::prelude::*;
810

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

164+
#[cfg(feature = "export")]
165+
impl<P0, P1, F> ExportableProvider for ForkByErrorProvider<P0, P1, F>
166+
where
167+
P0: ExportableProvider,
168+
P1: ExportableProvider,
169+
F: ForkByErrorPredicate + Sync,
170+
{
171+
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
172+
let mut markers = self.0.supported_markers();
173+
markers.extend(self.1.supported_markers());
174+
markers
175+
}
176+
}
177+
162178
/// A provider that returns data from the first child provider passing a predicate function.
163179
///
164180
/// This is an abstract forking provider that must be provided with a type implementing the
@@ -334,3 +350,17 @@ where
334350
Err(last_error)
335351
}
336352
}
353+
354+
#[cfg(feature = "export")]
355+
impl<P, F> ExportableProvider for MultiForkByErrorProvider<P, F>
356+
where
357+
P: ExportableProvider,
358+
F: ForkByErrorPredicate + Sync,
359+
{
360+
fn supported_markers(&self) -> std::collections::HashSet<DataMarkerInfo> {
361+
self.providers
362+
.iter()
363+
.flat_map(|p| p.supported_markers())
364+
.collect()
365+
}
366+
}

provider/adapters/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! - Use the [`fallback`] module to automatically resolve arbitrary locales for data loading.
1111
1212
// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
13-
#![cfg_attr(not(test), no_std)]
13+
#![cfg_attr(not(any(test, feature = "std")), no_std)]
1414
#![cfg_attr(
1515
not(test),
1616
deny(

tools/md-tests/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ edition = "2021"
1010

1111
[dev-dependencies]
1212
icu = { workspace = true, features = ["compiled_data", "serde"] }
13-
icu_provider_export = { workspace = true }
13+
icu_provider_export = { workspace = true, features = ["blob_exporter"] }
14+
icu_provider_source = { workspace = true, features = ["networking"] }
1415
icu_provider = { workspace = true, features = ["deserialize_json"] }
15-
icu_provider_adapters = { workspace = true, features = ["serde"] }
16+
icu_provider_adapters = { workspace = true, features = ["serde", "export"] }
1617
icu_provider_blob = { workspace = true }
1718
icu_provider_fs = { workspace = true }
1819

tutorials/data_provider.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,83 @@ assert_eq!(formatter.format_to_string(&100007i64.into()), "100🐮007");
253253

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

256+
## Exporting Custom Data Markers
257+
258+
To add custom data markers to your baked data or postcard file, create a forking exportable provider:
259+
260+
```rust
261+
use icu::locale::locale;
262+
use icu::plurals::provider::CardinalV1Marker;
263+
use icu_provider::prelude::*;
264+
use icu_provider::DataMarker;
265+
use icu_provider_adapters::fork::ForkByMarkerProvider;
266+
use icu_provider_blob::BlobDataProvider;
267+
use icu_provider_export::blob_exporter::BlobExporter;
268+
use icu_provider_export::prelude::*;
269+
use icu_provider_source::SourceDataProvider;
270+
use std::borrow::Cow;
271+
use std::collections::BTreeSet;
272+
273+
#[icu_provider::data_struct(marker(CustomMarker, "x/custom@1"))]
274+
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize, databake::Bake)]
275+
#[databake(path = crate)]
276+
pub struct Custom<'data> {
277+
message: Cow<'data, str>,
278+
};
279+
280+
struct CustomProvider;
281+
impl DataProvider<CustomMarker> for CustomProvider {
282+
fn load(&self, req: DataRequest) -> Result<DataResponse<CustomMarker>, DataError> {
283+
Ok(DataResponse {
284+
metadata: Default::default(),
285+
payload: DataPayload::from_owned(Custom {
286+
message: format!("Custom data for locale {}!", req.id.locale).into(),
287+
}),
288+
})
289+
}
290+
}
291+
292+
impl IterableDataProvider<CustomMarker> for CustomProvider {
293+
fn iter_ids(&self) -> Result<BTreeSet<DataIdentifierCow>, DataError> {
294+
Ok([locale!("es"), locale!("ja")]
295+
.into_iter()
296+
.map(DataLocale::from)
297+
.map(DataIdentifierCow::from_locale)
298+
.collect())
299+
}
300+
}
301+
302+
icu_provider::export::make_exportable_provider!(CustomProvider, [CustomMarker,]);
303+
304+
let icu4x_source_provider = SourceDataProvider::new_latest_tested();
305+
let custom_source_provider = CustomProvider;
306+
307+
let mut buffer = Vec::<u8>::new();
308+
309+
ExportDriver::new(
310+
[DataLocaleFamily::FULL],
311+
DeduplicationStrategy::None.into(),
312+
LocaleFallbacker::try_new_unstable(&icu4x_source_provider).unwrap(),
313+
)
314+
.with_markers([CardinalV1Marker::INFO, CustomMarker::INFO])
315+
.export(
316+
&ForkByMarkerProvider::new(icu4x_source_provider, custom_source_provider),
317+
BlobExporter::new_v2_with_sink(Box::new(&mut buffer)),
318+
)
319+
.unwrap();
320+
321+
let blob_provider = BlobDataProvider::try_new_from_blob(buffer.into()).unwrap();
322+
323+
let locale = DataLocale::from(&locale!("ja"));
324+
let req = DataRequest {
325+
id: DataIdentifierBorrowed::for_locale(&locale),
326+
metadata: Default::default(),
327+
};
328+
329+
assert!(blob_provider.load_data(CardinalV1Marker::INFO, req).is_ok());
330+
assert!(blob_provider.load_data(CustomMarker::INFO, req).is_ok());
331+
```
332+
256333
## Accessing the Resolved Locale
257334

258335
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.

0 commit comments

Comments
 (0)