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 crate::error::DataError; |
6 | use crate::key::DataKey; |
7 | use crate::marker::{DataMarker, KeyedDataMarker}; |
8 | use crate::request::DataRequest; |
9 | use crate::response::DataResponse; |
10 | |
11 | /// A data provider that loads data for a specific data type. |
12 | /// |
13 | /// Unlike [`DataProvider`], there may be multiple keys corresponding to the same data type. |
14 | /// This is often the case when returning `dyn` trait objects such as [`AnyMarker`]. |
15 | /// |
16 | /// [`AnyMarker`]: crate::any::AnyMarker |
17 | pub trait DynamicDataProvider<M> |
18 | where |
19 | M: DataMarker, |
20 | { |
21 | /// Query the provider for data, returning the result. |
22 | /// |
23 | /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an |
24 | /// Error with more information. |
25 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError>; |
26 | } |
27 | |
28 | /// A data provider that loads data for a specific [`DataKey`]. |
29 | pub trait DataProvider<M> |
30 | where |
31 | M: KeyedDataMarker, |
32 | { |
33 | /// Query the provider for data, returning the result. |
34 | /// |
35 | /// Returns [`Ok`] if the request successfully loaded data. If data failed to load, returns an |
36 | /// Error with more information. |
37 | fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError>; |
38 | } |
39 | |
40 | impl<M, P> DynamicDataProvider<M> for alloc::boxed::Box<P> |
41 | where |
42 | M: DataMarker, |
43 | P: DynamicDataProvider<M> + ?Sized, |
44 | { |
45 | fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
46 | (**self).load_data(key, req) |
47 | } |
48 | } |
49 | |
50 | #[cfg (test)] |
51 | mod test { |
52 | |
53 | use super::*; |
54 | use crate::hello_world::*; |
55 | use crate::prelude::*; |
56 | use alloc::borrow::Cow; |
57 | use alloc::string::String; |
58 | use core::fmt::Debug; |
59 | use serde::{Deserialize, Serialize}; |
60 | |
61 | // This tests DataProvider borrow semantics with a dummy data provider based on a |
62 | // JSON string. It also exercises most of the data provider code paths. |
63 | |
64 | /// Key for HelloAlt, used for testing mismatched types |
65 | const HELLO_ALT_KEY: DataKey = crate::data_key!("core/helloalt@1" ); |
66 | |
67 | /// A data struct serialization-compatible with HelloWorldV1 used for testing mismatched types |
68 | #[derive ( |
69 | Serialize, Deserialize, Debug, Clone, Default, PartialEq, yoke::Yokeable, zerofrom::ZeroFrom, |
70 | )] |
71 | struct HelloAlt { |
72 | #[zerofrom(clone)] |
73 | message: String, |
74 | } |
75 | |
76 | /// Marker type for [`HelloAlt`]. |
77 | struct HelloAltMarker {} |
78 | |
79 | impl DataMarker for HelloAltMarker { |
80 | type Yokeable = HelloAlt; |
81 | } |
82 | |
83 | impl KeyedDataMarker for HelloAltMarker { |
84 | const KEY: DataKey = HELLO_ALT_KEY; |
85 | } |
86 | |
87 | #[derive (Deserialize, Debug, Clone, Default, PartialEq)] |
88 | struct HelloCombined<'data> { |
89 | #[serde(borrow)] |
90 | pub hello_v1: HelloWorldV1<'data>, |
91 | pub hello_alt: HelloAlt, |
92 | } |
93 | |
94 | /// A DataProvider that owns its data, returning an Rc-variant DataPayload. |
95 | /// Supports only key::HELLO_WORLD_V1. Uses `impl_dynamic_data_provider!()`. |
96 | #[derive (Debug)] |
97 | struct DataWarehouse { |
98 | hello_v1: HelloWorldV1<'static>, |
99 | hello_alt: HelloAlt, |
100 | } |
101 | |
102 | impl DataProvider<HelloWorldV1Marker> for DataWarehouse { |
103 | fn load(&self, _: DataRequest) -> Result<DataResponse<HelloWorldV1Marker>, DataError> { |
104 | Ok(DataResponse { |
105 | metadata: DataResponseMetadata::default(), |
106 | payload: Some(DataPayload::from_owned(self.hello_v1.clone())), |
107 | }) |
108 | } |
109 | } |
110 | |
111 | crate::impl_dynamic_data_provider!(DataWarehouse, [HelloWorldV1Marker,], AnyMarker); |
112 | |
113 | /// A DataProvider that supports both key::HELLO_WORLD_V1 and HELLO_ALT. |
114 | #[derive (Debug)] |
115 | struct DataProvider2 { |
116 | data: DataWarehouse, |
117 | } |
118 | |
119 | impl From<DataWarehouse> for DataProvider2 { |
120 | fn from(warehouse: DataWarehouse) -> Self { |
121 | DataProvider2 { data: warehouse } |
122 | } |
123 | } |
124 | |
125 | impl DataProvider<HelloWorldV1Marker> for DataProvider2 { |
126 | fn load(&self, _: DataRequest) -> Result<DataResponse<HelloWorldV1Marker>, DataError> { |
127 | Ok(DataResponse { |
128 | metadata: DataResponseMetadata::default(), |
129 | payload: Some(DataPayload::from_owned(self.data.hello_v1.clone())), |
130 | }) |
131 | } |
132 | } |
133 | |
134 | impl DataProvider<HelloAltMarker> for DataProvider2 { |
135 | fn load(&self, _: DataRequest) -> Result<DataResponse<HelloAltMarker>, DataError> { |
136 | Ok(DataResponse { |
137 | metadata: DataResponseMetadata::default(), |
138 | payload: Some(DataPayload::from_owned(self.data.hello_alt.clone())), |
139 | }) |
140 | } |
141 | } |
142 | |
143 | crate::impl_dynamic_data_provider!( |
144 | DataProvider2, |
145 | [HelloWorldV1Marker, HelloAltMarker,], |
146 | AnyMarker |
147 | ); |
148 | |
149 | const DATA: &str = r#"{ |
150 | "hello_v1": { |
151 | "message": "Hello V1" |
152 | }, |
153 | "hello_alt": { |
154 | "message": "Hello Alt" |
155 | } |
156 | }"# ; |
157 | |
158 | fn get_warehouse(data: &'static str) -> DataWarehouse { |
159 | let data: HelloCombined = serde_json::from_str(data).expect("Well-formed data" ); |
160 | DataWarehouse { |
161 | hello_v1: data.hello_v1, |
162 | hello_alt: data.hello_alt, |
163 | } |
164 | } |
165 | |
166 | fn get_payload_v1<P: DataProvider<HelloWorldV1Marker> + ?Sized>( |
167 | provider: &P, |
168 | ) -> Result<DataPayload<HelloWorldV1Marker>, DataError> { |
169 | provider.load(Default::default())?.take_payload() |
170 | } |
171 | |
172 | fn get_payload_alt<P: DataProvider<HelloAltMarker> + ?Sized>( |
173 | provider: &P, |
174 | ) -> Result<DataPayload<HelloAltMarker>, DataError> { |
175 | provider.load(Default::default())?.take_payload() |
176 | } |
177 | |
178 | #[test ] |
179 | fn test_warehouse_owned() { |
180 | let warehouse = get_warehouse(DATA); |
181 | let hello_data = get_payload_v1(&warehouse).unwrap(); |
182 | assert!(matches!( |
183 | hello_data.get(), |
184 | HelloWorldV1 { |
185 | message: Cow::Borrowed(_), |
186 | } |
187 | )); |
188 | } |
189 | |
190 | #[test ] |
191 | fn test_warehouse_owned_dyn_erased() { |
192 | let warehouse = get_warehouse(DATA); |
193 | let hello_data = get_payload_v1(&warehouse.as_any_provider().as_downcasting()).unwrap(); |
194 | assert!(matches!( |
195 | hello_data.get(), |
196 | HelloWorldV1 { |
197 | message: Cow::Borrowed(_), |
198 | } |
199 | )); |
200 | } |
201 | |
202 | #[test ] |
203 | fn test_warehouse_owned_dyn_generic() { |
204 | let warehouse = get_warehouse(DATA); |
205 | let hello_data = |
206 | get_payload_v1(&warehouse as &dyn DataProvider<HelloWorldV1Marker>).unwrap(); |
207 | assert!(matches!( |
208 | hello_data.get(), |
209 | HelloWorldV1 { |
210 | message: Cow::Borrowed(_), |
211 | } |
212 | )); |
213 | } |
214 | |
215 | #[test ] |
216 | fn test_warehouse_owned_dyn_erased_alt() { |
217 | let warehouse = get_warehouse(DATA); |
218 | let response = get_payload_alt(&warehouse.as_any_provider().as_downcasting()); |
219 | assert!(matches!( |
220 | response, |
221 | Err(DataError { |
222 | kind: DataErrorKind::MissingDataKey, |
223 | .. |
224 | }) |
225 | )); |
226 | } |
227 | |
228 | #[test ] |
229 | fn test_provider2() { |
230 | let warehouse = get_warehouse(DATA); |
231 | let provider = DataProvider2::from(warehouse); |
232 | let hello_data = get_payload_v1(&provider).unwrap(); |
233 | assert!(matches!( |
234 | hello_data.get(), |
235 | HelloWorldV1 { |
236 | message: Cow::Borrowed(_), |
237 | } |
238 | )); |
239 | } |
240 | |
241 | #[test ] |
242 | fn test_provider2_dyn_erased() { |
243 | let warehouse = get_warehouse(DATA); |
244 | let provider = DataProvider2::from(warehouse); |
245 | let hello_data = get_payload_v1(&provider.as_any_provider().as_downcasting()).unwrap(); |
246 | assert!(matches!( |
247 | hello_data.get(), |
248 | HelloWorldV1 { |
249 | message: Cow::Borrowed(_), |
250 | } |
251 | )); |
252 | } |
253 | |
254 | #[test ] |
255 | fn test_provider2_dyn_erased_alt() { |
256 | let warehouse = get_warehouse(DATA); |
257 | let provider = DataProvider2::from(warehouse); |
258 | let hello_data = get_payload_alt(&provider.as_any_provider().as_downcasting()).unwrap(); |
259 | assert!(matches!(hello_data.get(), HelloAlt { .. })); |
260 | } |
261 | |
262 | #[test ] |
263 | fn test_provider2_dyn_generic() { |
264 | let warehouse = get_warehouse(DATA); |
265 | let provider = DataProvider2::from(warehouse); |
266 | let hello_data = |
267 | get_payload_v1(&provider as &dyn DataProvider<HelloWorldV1Marker>).unwrap(); |
268 | assert!(matches!( |
269 | hello_data.get(), |
270 | HelloWorldV1 { |
271 | message: Cow::Borrowed(_), |
272 | } |
273 | )); |
274 | } |
275 | |
276 | #[test ] |
277 | fn test_provider2_dyn_generic_alt() { |
278 | let warehouse = get_warehouse(DATA); |
279 | let provider = DataProvider2::from(warehouse); |
280 | let hello_data = get_payload_alt(&provider as &dyn DataProvider<HelloAltMarker>).unwrap(); |
281 | assert!(matches!(hello_data.get(), HelloAlt { .. })); |
282 | } |
283 | |
284 | #[test ] |
285 | fn test_mismatched_types() { |
286 | let warehouse = get_warehouse(DATA); |
287 | let provider = DataProvider2::from(warehouse); |
288 | // Request is for v2, but type argument is for v1 |
289 | let response: Result<DataResponse<HelloWorldV1Marker>, DataError> = AnyProvider::load_any( |
290 | &provider.as_any_provider(), |
291 | HELLO_ALT_KEY, |
292 | Default::default(), |
293 | ) |
294 | .unwrap() |
295 | .downcast(); |
296 | assert!(matches!( |
297 | response, |
298 | Err(DataError { |
299 | kind: DataErrorKind::MismatchedType(_), |
300 | .. |
301 | }) |
302 | )); |
303 | } |
304 | |
305 | fn check_v1_v2<P>(d: &P) |
306 | where |
307 | P: DataProvider<HelloWorldV1Marker> + DataProvider<HelloAltMarker> + ?Sized, |
308 | { |
309 | let v1: DataPayload<HelloWorldV1Marker> = |
310 | d.load(Default::default()).unwrap().take_payload().unwrap(); |
311 | let v2: DataPayload<HelloAltMarker> = |
312 | d.load(Default::default()).unwrap().take_payload().unwrap(); |
313 | if v1.get().message == v2.get().message { |
314 | panic!() |
315 | } |
316 | } |
317 | |
318 | #[test ] |
319 | fn test_v1_v2_generic() { |
320 | let warehouse = get_warehouse(DATA); |
321 | let provider = DataProvider2::from(warehouse); |
322 | check_v1_v2(&provider); |
323 | } |
324 | |
325 | #[test ] |
326 | fn test_v1_v2_dyn_erased() { |
327 | let warehouse = get_warehouse(DATA); |
328 | let provider = DataProvider2::from(warehouse); |
329 | check_v1_v2(&provider.as_any_provider().as_downcasting()); |
330 | } |
331 | } |
332 | |