| 1 | // This file is part of ICU4X. For terms of use, please see the file |
| 2 | // called LICENSE at the top level of the ICU4X source tree |
| 3 | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
| 4 | |
| 5 | use core::marker::PhantomData; |
| 6 | use yoke::Yokeable; |
| 7 | |
| 8 | use crate::error::DataError; |
| 9 | use crate::key::DataKey; |
| 10 | use crate::marker::{DataMarker, KeyedDataMarker}; |
| 11 | use crate::request::DataRequest; |
| 12 | use crate::response::DataResponse; |
| 13 | |
| 14 | /// A data provider that loads data for a specific [`DataKey`]. |
| 15 | pub trait DataProvider<M> |
| 16 | where |
| 17 | M: KeyedDataMarker, |
| 18 | { |
| 19 | /// Query the provider for data, returning the result. |
| 20 | /// |
| 21 | /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an |
| 22 | /// Error with more information. |
| 23 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError>; |
| 24 | } |
| 25 | |
| 26 | impl<'a, M, P> DataProvider<M> for &'a P |
| 27 | where |
| 28 | M: KeyedDataMarker, |
| 29 | P: DataProvider<M> + ?Sized, |
| 30 | { |
| 31 | #[inline ] |
| 32 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 33 | (*self).load(req) |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | impl<M, P> DataProvider<M> for alloc::boxed::Box<P> |
| 38 | where |
| 39 | M: KeyedDataMarker, |
| 40 | P: DataProvider<M> + ?Sized, |
| 41 | { |
| 42 | #[inline ] |
| 43 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 44 | (**self).load(req) |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | impl<M, P> DataProvider<M> for alloc::rc::Rc<P> |
| 49 | where |
| 50 | M: KeyedDataMarker, |
| 51 | P: DataProvider<M> + ?Sized, |
| 52 | { |
| 53 | #[inline ] |
| 54 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 55 | (**self).load(req) |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | #[cfg (target_has_atomic = "ptr" )] |
| 60 | impl<M, P> DataProvider<M> for alloc::sync::Arc<P> |
| 61 | where |
| 62 | M: KeyedDataMarker, |
| 63 | P: DataProvider<M> + ?Sized, |
| 64 | { |
| 65 | #[inline ] |
| 66 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 67 | (**self).load(req) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | /// A data provider that loads data for a specific data type. |
| 72 | /// |
| 73 | /// Unlike [`DataProvider`], there may be multiple keys corresponding to the same data type. |
| 74 | /// This is often the case when returning `dyn` trait objects such as [`AnyMarker`]. |
| 75 | /// |
| 76 | /// [`AnyMarker`]: crate::any::AnyMarker |
| 77 | pub trait DynamicDataProvider<M> |
| 78 | where |
| 79 | M: DataMarker, |
| 80 | { |
| 81 | /// Query the provider for data, returning the result. |
| 82 | /// |
| 83 | /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an |
| 84 | /// Error with more information. |
| 85 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError>; |
| 86 | } |
| 87 | |
| 88 | impl<'a, M, P> DynamicDataProvider<M> for &'a P |
| 89 | where |
| 90 | M: DataMarker, |
| 91 | P: DynamicDataProvider<M> + ?Sized, |
| 92 | { |
| 93 | #[inline ] |
| 94 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 95 | (*self).load_data(key, req) |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | impl<M, P> DynamicDataProvider<M> for alloc::boxed::Box<P> |
| 100 | where |
| 101 | M: DataMarker, |
| 102 | P: DynamicDataProvider<M> + ?Sized, |
| 103 | { |
| 104 | #[inline ] |
| 105 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 106 | (**self).load_data(key, req) |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | impl<M, P> DynamicDataProvider<M> for alloc::rc::Rc<P> |
| 111 | where |
| 112 | M: DataMarker, |
| 113 | P: DynamicDataProvider<M> + ?Sized, |
| 114 | { |
| 115 | #[inline ] |
| 116 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 117 | (**self).load_data(key, req) |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | #[cfg (target_has_atomic = "ptr" )] |
| 122 | impl<M, P> DynamicDataProvider<M> for alloc::sync::Arc<P> |
| 123 | where |
| 124 | M: DataMarker, |
| 125 | P: DynamicDataProvider<M> + ?Sized, |
| 126 | { |
| 127 | #[inline ] |
| 128 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 129 | (**self).load_data(key, req) |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// A data provider that loads data for a specific data type. |
| 134 | /// |
| 135 | /// Unlike [`DataProvider`], the provider is bound to a specific key ahead of time. |
| 136 | /// |
| 137 | /// This crate provides [`DataProviderWithKey`] which implements this trait on a single provider |
| 138 | /// with a single key. However, this trait can also be implemented on providers that fork between |
| 139 | /// multiple keys that all return the same data type. For example, it can abstract over many |
| 140 | /// calendar systems in the datetime formatter. |
| 141 | /// |
| 142 | /// [`AnyMarker`]: crate::any::AnyMarker |
| 143 | pub trait BoundDataProvider<M> |
| 144 | where |
| 145 | M: DataMarker, |
| 146 | { |
| 147 | /// Query the provider for data, returning the result. |
| 148 | /// |
| 149 | /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an |
| 150 | /// Error with more information. |
| 151 | fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError>; |
| 152 | /// Returns the [`DataKey`] that this provider uses for loading data. |
| 153 | fn bound_key(&self) -> DataKey; |
| 154 | } |
| 155 | |
| 156 | impl<'a, M, P> BoundDataProvider<M> for &'a P |
| 157 | where |
| 158 | M: DataMarker, |
| 159 | P: BoundDataProvider<M> + ?Sized, |
| 160 | { |
| 161 | #[inline ] |
| 162 | fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 163 | (*self).load_bound(req) |
| 164 | } |
| 165 | #[inline ] |
| 166 | fn bound_key(&self) -> DataKey { |
| 167 | (*self).bound_key() |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | impl<M, P> BoundDataProvider<M> for alloc::boxed::Box<P> |
| 172 | where |
| 173 | M: DataMarker, |
| 174 | P: BoundDataProvider<M> + ?Sized, |
| 175 | { |
| 176 | #[inline ] |
| 177 | fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 178 | (**self).load_bound(req) |
| 179 | } |
| 180 | #[inline ] |
| 181 | fn bound_key(&self) -> DataKey { |
| 182 | (**self).bound_key() |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | impl<M, P> BoundDataProvider<M> for alloc::rc::Rc<P> |
| 187 | where |
| 188 | M: DataMarker, |
| 189 | P: BoundDataProvider<M> + ?Sized, |
| 190 | { |
| 191 | #[inline ] |
| 192 | fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 193 | (**self).load_bound(req) |
| 194 | } |
| 195 | #[inline ] |
| 196 | fn bound_key(&self) -> DataKey { |
| 197 | (**self).bound_key() |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | #[cfg (target_has_atomic = "ptr" )] |
| 202 | impl<M, P> BoundDataProvider<M> for alloc::sync::Arc<P> |
| 203 | where |
| 204 | M: DataMarker, |
| 205 | P: BoundDataProvider<M> + ?Sized, |
| 206 | { |
| 207 | #[inline ] |
| 208 | fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
| 209 | (**self).load_bound(req) |
| 210 | } |
| 211 | #[inline ] |
| 212 | fn bound_key(&self) -> DataKey { |
| 213 | (**self).bound_key() |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | /// A [`DataProvider`] associated with a specific key. |
| 218 | /// |
| 219 | /// Implements [`BoundDataProvider`]. |
| 220 | #[derive (Debug)] |
| 221 | pub struct DataProviderWithKey<M, P> { |
| 222 | inner: P, |
| 223 | _marker: PhantomData<M>, |
| 224 | } |
| 225 | |
| 226 | impl<M, P> DataProviderWithKey<M, P> |
| 227 | where |
| 228 | M: KeyedDataMarker, |
| 229 | P: DataProvider<M>, |
| 230 | { |
| 231 | /// Creates a [`DataProviderWithKey`] from a [`DataProvider`] with a [`KeyedDataMarker`]. |
| 232 | pub const fn new(inner: P) -> Self { |
| 233 | Self { |
| 234 | inner, |
| 235 | _marker: PhantomData, |
| 236 | } |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | impl<M, M0, Y, P> BoundDataProvider<M0> for DataProviderWithKey<M, P> |
| 241 | where |
| 242 | M: KeyedDataMarker<Yokeable = Y>, |
| 243 | M0: DataMarker<Yokeable = Y>, |
| 244 | Y: for<'a> dynYokeable<'a>, |
| 245 | P: DataProvider<M>, |
| 246 | { |
| 247 | #[inline ] |
| 248 | fn load_bound(&self, req: DataRequest) -> Result<DataResponse<M0>, DataError> { |
| 249 | self.inner.load(req).map(op:DataResponse::cast) |
| 250 | } |
| 251 | #[inline ] |
| 252 | fn bound_key(&self) -> DataKey { |
| 253 | M::KEY |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | #[cfg (test)] |
| 258 | mod test { |
| 259 | |
| 260 | use super::*; |
| 261 | use crate::hello_world::*; |
| 262 | use crate::prelude::*; |
| 263 | use alloc::borrow::Cow; |
| 264 | use alloc::string::String; |
| 265 | use core::fmt::Debug; |
| 266 | use serde::{Deserialize, Serialize}; |
| 267 | |
| 268 | // This tests DataProvider borrow semantics with a dummy data provider based on a |
| 269 | // JSON string. It also exercises most of the data provider code paths. |
| 270 | |
| 271 | /// Key for HelloAlt, used for testing mismatched types |
| 272 | const HELLO_ALT_KEY: DataKey = crate::data_key!("core/helloalt@1" ); |
| 273 | |
| 274 | /// A data struct serialization-compatible with HelloWorldV1 used for testing mismatched types |
| 275 | #[derive ( |
| 276 | Serialize, Deserialize, Debug, Clone, Default, PartialEq, yoke::Yokeable, zerofrom::ZeroFrom, |
| 277 | )] |
| 278 | struct HelloAlt { |
| 279 | #[zerofrom(clone)] |
| 280 | message: String, |
| 281 | } |
| 282 | |
| 283 | /// Marker type for [`HelloAlt`]. |
| 284 | struct HelloAltMarker {} |
| 285 | |
| 286 | impl DataMarker for HelloAltMarker { |
| 287 | type Yokeable = HelloAlt; |
| 288 | } |
| 289 | |
| 290 | impl KeyedDataMarker for HelloAltMarker { |
| 291 | const KEY: DataKey = HELLO_ALT_KEY; |
| 292 | } |
| 293 | |
| 294 | #[derive (Deserialize, Debug, Clone, Default, PartialEq)] |
| 295 | struct HelloCombined<'data> { |
| 296 | #[serde(borrow)] |
| 297 | pub hello_v1: HelloWorldV1<'data>, |
| 298 | pub hello_alt: HelloAlt, |
| 299 | } |
| 300 | |
| 301 | /// A DataProvider that owns its data, returning an Rc-variant DataPayload. |
| 302 | /// Supports only key::HELLO_WORLD_V1. Uses `impl_dynamic_data_provider!()`. |
| 303 | #[derive (Debug)] |
| 304 | struct DataWarehouse { |
| 305 | hello_v1: HelloWorldV1<'static>, |
| 306 | hello_alt: HelloAlt, |
| 307 | } |
| 308 | |
| 309 | impl DataProvider<HelloWorldV1Marker> for DataWarehouse { |
| 310 | fn load(&self, _: DataRequest) -> Result<DataResponse<HelloWorldV1Marker>, DataError> { |
| 311 | Ok(DataResponse { |
| 312 | metadata: DataResponseMetadata::default(), |
| 313 | payload: Some(DataPayload::from_owned(self.hello_v1.clone())), |
| 314 | }) |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | crate::impl_dynamic_data_provider!(DataWarehouse, [HelloWorldV1Marker,], AnyMarker); |
| 319 | |
| 320 | /// A DataProvider that supports both key::HELLO_WORLD_V1 and HELLO_ALT. |
| 321 | #[derive (Debug)] |
| 322 | struct DataProvider2 { |
| 323 | data: DataWarehouse, |
| 324 | } |
| 325 | |
| 326 | impl From<DataWarehouse> for DataProvider2 { |
| 327 | fn from(warehouse: DataWarehouse) -> Self { |
| 328 | DataProvider2 { data: warehouse } |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | impl DataProvider<HelloWorldV1Marker> for DataProvider2 { |
| 333 | fn load(&self, _: DataRequest) -> Result<DataResponse<HelloWorldV1Marker>, DataError> { |
| 334 | Ok(DataResponse { |
| 335 | metadata: DataResponseMetadata::default(), |
| 336 | payload: Some(DataPayload::from_owned(self.data.hello_v1.clone())), |
| 337 | }) |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | impl DataProvider<HelloAltMarker> for DataProvider2 { |
| 342 | fn load(&self, _: DataRequest) -> Result<DataResponse<HelloAltMarker>, DataError> { |
| 343 | Ok(DataResponse { |
| 344 | metadata: DataResponseMetadata::default(), |
| 345 | payload: Some(DataPayload::from_owned(self.data.hello_alt.clone())), |
| 346 | }) |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | crate::impl_dynamic_data_provider!( |
| 351 | DataProvider2, |
| 352 | [HelloWorldV1Marker, HelloAltMarker,], |
| 353 | AnyMarker |
| 354 | ); |
| 355 | |
| 356 | const DATA: &str = r#"{ |
| 357 | "hello_v1": { |
| 358 | "message": "Hello V1" |
| 359 | }, |
| 360 | "hello_alt": { |
| 361 | "message": "Hello Alt" |
| 362 | } |
| 363 | }"# ; |
| 364 | |
| 365 | fn get_warehouse(data: &'static str) -> DataWarehouse { |
| 366 | let data: HelloCombined = serde_json::from_str(data).expect("Well-formed data" ); |
| 367 | DataWarehouse { |
| 368 | hello_v1: data.hello_v1, |
| 369 | hello_alt: data.hello_alt, |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | fn get_payload_v1<P: DataProvider<HelloWorldV1Marker> + ?Sized>( |
| 374 | provider: &P, |
| 375 | ) -> Result<DataPayload<HelloWorldV1Marker>, DataError> { |
| 376 | provider.load(Default::default())?.take_payload() |
| 377 | } |
| 378 | |
| 379 | fn get_payload_alt<P: DataProvider<HelloAltMarker> + ?Sized>( |
| 380 | provider: &P, |
| 381 | ) -> Result<DataPayload<HelloAltMarker>, DataError> { |
| 382 | provider.load(Default::default())?.take_payload() |
| 383 | } |
| 384 | |
| 385 | #[test ] |
| 386 | fn test_warehouse_owned() { |
| 387 | let warehouse = get_warehouse(DATA); |
| 388 | let hello_data = get_payload_v1(&warehouse).unwrap(); |
| 389 | assert!(matches!( |
| 390 | hello_data.get(), |
| 391 | HelloWorldV1 { |
| 392 | message: Cow::Borrowed(_), |
| 393 | } |
| 394 | )); |
| 395 | } |
| 396 | |
| 397 | #[test ] |
| 398 | fn test_warehouse_owned_dyn_erased() { |
| 399 | let warehouse = get_warehouse(DATA); |
| 400 | let hello_data = get_payload_v1(&warehouse.as_any_provider().as_downcasting()).unwrap(); |
| 401 | assert!(matches!( |
| 402 | hello_data.get(), |
| 403 | HelloWorldV1 { |
| 404 | message: Cow::Borrowed(_), |
| 405 | } |
| 406 | )); |
| 407 | } |
| 408 | |
| 409 | #[test ] |
| 410 | fn test_warehouse_owned_dyn_generic() { |
| 411 | let warehouse = get_warehouse(DATA); |
| 412 | let hello_data = |
| 413 | get_payload_v1(&warehouse as &dyn DataProvider<HelloWorldV1Marker>).unwrap(); |
| 414 | assert!(matches!( |
| 415 | hello_data.get(), |
| 416 | HelloWorldV1 { |
| 417 | message: Cow::Borrowed(_), |
| 418 | } |
| 419 | )); |
| 420 | } |
| 421 | |
| 422 | #[test ] |
| 423 | fn test_warehouse_owned_dyn_erased_alt() { |
| 424 | let warehouse = get_warehouse(DATA); |
| 425 | let response = get_payload_alt(&warehouse.as_any_provider().as_downcasting()); |
| 426 | assert!(matches!( |
| 427 | response, |
| 428 | Err(DataError { |
| 429 | kind: DataErrorKind::MissingDataKey, |
| 430 | .. |
| 431 | }) |
| 432 | )); |
| 433 | } |
| 434 | |
| 435 | #[test ] |
| 436 | fn test_provider2() { |
| 437 | let warehouse = get_warehouse(DATA); |
| 438 | let provider = DataProvider2::from(warehouse); |
| 439 | let hello_data = get_payload_v1(&provider).unwrap(); |
| 440 | assert!(matches!( |
| 441 | hello_data.get(), |
| 442 | HelloWorldV1 { |
| 443 | message: Cow::Borrowed(_), |
| 444 | } |
| 445 | )); |
| 446 | } |
| 447 | |
| 448 | #[test ] |
| 449 | fn test_provider2_dyn_erased() { |
| 450 | let warehouse = get_warehouse(DATA); |
| 451 | let provider = DataProvider2::from(warehouse); |
| 452 | let hello_data = get_payload_v1(&provider.as_any_provider().as_downcasting()).unwrap(); |
| 453 | assert!(matches!( |
| 454 | hello_data.get(), |
| 455 | HelloWorldV1 { |
| 456 | message: Cow::Borrowed(_), |
| 457 | } |
| 458 | )); |
| 459 | } |
| 460 | |
| 461 | #[test ] |
| 462 | fn test_provider2_dyn_erased_alt() { |
| 463 | let warehouse = get_warehouse(DATA); |
| 464 | let provider = DataProvider2::from(warehouse); |
| 465 | let hello_data = get_payload_alt(&provider.as_any_provider().as_downcasting()).unwrap(); |
| 466 | assert!(matches!(hello_data.get(), HelloAlt { .. })); |
| 467 | } |
| 468 | |
| 469 | #[test ] |
| 470 | fn test_provider2_dyn_generic() { |
| 471 | let warehouse = get_warehouse(DATA); |
| 472 | let provider = DataProvider2::from(warehouse); |
| 473 | let hello_data = |
| 474 | get_payload_v1(&provider as &dyn DataProvider<HelloWorldV1Marker>).unwrap(); |
| 475 | assert!(matches!( |
| 476 | hello_data.get(), |
| 477 | HelloWorldV1 { |
| 478 | message: Cow::Borrowed(_), |
| 479 | } |
| 480 | )); |
| 481 | } |
| 482 | |
| 483 | #[test ] |
| 484 | fn test_provider2_dyn_generic_alt() { |
| 485 | let warehouse = get_warehouse(DATA); |
| 486 | let provider = DataProvider2::from(warehouse); |
| 487 | let hello_data = get_payload_alt(&provider as &dyn DataProvider<HelloAltMarker>).unwrap(); |
| 488 | assert!(matches!(hello_data.get(), HelloAlt { .. })); |
| 489 | } |
| 490 | |
| 491 | #[test ] |
| 492 | fn test_mismatched_types() { |
| 493 | let warehouse = get_warehouse(DATA); |
| 494 | let provider = DataProvider2::from(warehouse); |
| 495 | // Request is for v2, but type argument is for v1 |
| 496 | let response: Result<DataResponse<HelloWorldV1Marker>, DataError> = AnyProvider::load_any( |
| 497 | &provider.as_any_provider(), |
| 498 | HELLO_ALT_KEY, |
| 499 | Default::default(), |
| 500 | ) |
| 501 | .unwrap() |
| 502 | .downcast(); |
| 503 | assert!(matches!( |
| 504 | response, |
| 505 | Err(DataError { |
| 506 | kind: DataErrorKind::MismatchedType(_), |
| 507 | .. |
| 508 | }) |
| 509 | )); |
| 510 | } |
| 511 | |
| 512 | fn check_v1_v2<P>(d: &P) |
| 513 | where |
| 514 | P: DataProvider<HelloWorldV1Marker> + DataProvider<HelloAltMarker> + ?Sized, |
| 515 | { |
| 516 | let v1: DataPayload<HelloWorldV1Marker> = |
| 517 | d.load(Default::default()).unwrap().take_payload().unwrap(); |
| 518 | let v2: DataPayload<HelloAltMarker> = |
| 519 | d.load(Default::default()).unwrap().take_payload().unwrap(); |
| 520 | if v1.get().message == v2.get().message { |
| 521 | panic!() |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | #[test ] |
| 526 | fn test_v1_v2_generic() { |
| 527 | let warehouse = get_warehouse(DATA); |
| 528 | let provider = DataProvider2::from(warehouse); |
| 529 | check_v1_v2(&provider); |
| 530 | } |
| 531 | |
| 532 | #[test ] |
| 533 | fn test_v1_v2_dyn_erased() { |
| 534 | let warehouse = get_warehouse(DATA); |
| 535 | let provider = DataProvider2::from(warehouse); |
| 536 | check_v1_v2(&provider.as_any_provider().as_downcasting()); |
| 537 | } |
| 538 | } |
| 539 | |