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::buf::BufferMarker; |
6 | use crate::error::{DataError, DataErrorKind}; |
7 | use crate::marker::DataMarker; |
8 | use crate::request::DataLocale; |
9 | use alloc::boxed::Box; |
10 | use core::convert::TryFrom; |
11 | use core::fmt::Debug; |
12 | use core::marker::PhantomData; |
13 | use core::ops::Deref; |
14 | use yoke::trait_hack::YokeTraitHack; |
15 | use yoke::*; |
16 | |
17 | #[cfg (not(feature = "sync" ))] |
18 | use alloc::rc::Rc as SelectedRc; |
19 | #[cfg (feature = "sync" )] |
20 | use alloc::sync::Arc as SelectedRc; |
21 | |
22 | /// A response object containing metadata about the returned data. |
23 | #[derive (Debug, Clone, PartialEq, Default)] |
24 | #[non_exhaustive ] |
25 | pub struct DataResponseMetadata { |
26 | /// The resolved locale of the returned data, if locale fallbacking was performed. |
27 | pub locale: Option<DataLocale>, |
28 | /// The format of the buffer for buffer-backed data, if known (for example, JSON). |
29 | pub buffer_format: Option<crate::buf::BufferFormat>, |
30 | } |
31 | |
32 | /// A container for data payloads returned from a data provider. |
33 | /// |
34 | /// [`DataPayload`] is built on top of the [`yoke`] framework, which allows for cheap, zero-copy |
35 | /// operations on data via the use of self-references. |
36 | /// |
37 | /// The type of the data stored in [`DataPayload`] is determined by the [`DataMarker`] type parameter. |
38 | /// |
39 | /// ## Accessing the data |
40 | /// |
41 | /// To get a reference to the data inside [`DataPayload`], use [`DataPayload::get()`]. If you need |
42 | /// to store the data for later use, you need to store the [`DataPayload`] itself, since `get` only |
43 | /// returns a reference with an ephemeral lifetime. |
44 | /// |
45 | /// ## Mutating the data |
46 | /// |
47 | /// To modify the data stored in a [`DataPayload`], use [`DataPayload::with_mut()`]. |
48 | /// |
49 | /// ## Transforming the data to a different type |
50 | /// |
51 | /// To transform a [`DataPayload`] to a different type backed by the same data store (cart), use |
52 | /// [`DataPayload::map_project()`] or one of its sister methods. |
53 | /// |
54 | /// # Cargo feature: `sync` |
55 | /// |
56 | /// By default, the payload uses non-concurrent reference counting internally, and hence is neither |
57 | /// [`Sync`] nor [`Send`]; if these traits are required, the `sync` Cargo feature can be enabled. |
58 | /// |
59 | /// # Examples |
60 | /// |
61 | /// Basic usage, using the `HelloWorldV1Marker` marker: |
62 | /// |
63 | /// ``` |
64 | /// use icu_provider::hello_world::*; |
65 | /// use icu_provider::prelude::*; |
66 | /// use std::borrow::Cow; |
67 | /// |
68 | /// let payload = DataPayload::<HelloWorldV1Marker>::from_owned(HelloWorldV1 { |
69 | /// message: Cow::Borrowed("Demo" ), |
70 | /// }); |
71 | /// |
72 | /// assert_eq!("Demo" , payload.get().message); |
73 | /// ``` |
74 | pub struct DataPayload<M: DataMarker>(pub(crate) DataPayloadInner<M>); |
75 | |
76 | pub(crate) enum DataPayloadInner<M: DataMarker> { |
77 | Yoke(Yoke<M::Yokeable, Option<Cart>>), |
78 | StaticRef(&'static M::Yokeable), |
79 | } |
80 | |
81 | /// The type of the "cart" that is used by `DataPayload`. |
82 | #[derive (Clone, Debug)] |
83 | #[allow (clippy::redundant_allocation)] // false positive, it's cheaper to wrap an existing Box in an Rc than to reallocate a huge Rc |
84 | pub struct Cart(SelectedRc<Box<[u8]>>); |
85 | |
86 | impl Deref for Cart { |
87 | type Target = Box<[u8]>; |
88 | fn deref(&self) -> &Self::Target { |
89 | &self.0 |
90 | } |
91 | } |
92 | // Safe because both Rc and Arc are StableDeref, and our impl delegates. |
93 | unsafe impl stable_deref_trait::StableDeref for Cart {} |
94 | // Safe because both Rc and Arc are CloneableCart, and our impl delegates. |
95 | unsafe impl yoke::CloneableCart for Cart {} |
96 | |
97 | impl Cart { |
98 | /// Creates a `Yoke<Y, Option<Cart>>` from owned bytes by applying `f`. |
99 | pub fn try_make_yoke<Y, F, E>(cart: Box<[u8]>, f: F) -> Result<Yoke<Y, Option<Self>>, E> |
100 | where |
101 | for<'a> Y: Yokeable<'a>, |
102 | F: FnOnce(&[u8]) -> Result<<Y as Yokeable>::Output, E>, |
103 | { |
104 | Yoke::try_attach_to_cart(SelectedRc::new(cart), |b| f(b)) |
105 | // Safe because the cart is only wrapped |
106 | .map(|yoke| unsafe { yoke.replace_cart(Cart) }) |
107 | .map(op:Yoke::wrap_cart_in_option) |
108 | } |
109 | } |
110 | |
111 | impl<M> Debug for DataPayload<M> |
112 | where |
113 | M: DataMarker, |
114 | for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug, |
115 | { |
116 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
117 | self.get().fmt(f) |
118 | } |
119 | } |
120 | |
121 | /// Cloning a DataPayload is generally a cheap operation. |
122 | /// See notes in the `Clone` impl for [`Yoke`]. |
123 | /// |
124 | /// # Examples |
125 | /// |
126 | /// ```no_run |
127 | /// use icu_provider::hello_world::*; |
128 | /// use icu_provider::prelude::*; |
129 | /// |
130 | /// let resp1: DataPayload<HelloWorldV1Marker> = todo!(); |
131 | /// let resp2 = resp1.clone(); |
132 | /// ``` |
133 | impl<M> Clone for DataPayload<M> |
134 | where |
135 | M: DataMarker, |
136 | for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone, |
137 | { |
138 | fn clone(&self) -> Self { |
139 | Self(match &self.0 { |
140 | DataPayloadInner::Yoke(yoke: &Yoke<::Yokeable, …>) => DataPayloadInner::Yoke(yoke.clone()), |
141 | DataPayloadInner::StaticRef(r: &&::Yokeable) => DataPayloadInner::StaticRef(*r), |
142 | }) |
143 | } |
144 | } |
145 | |
146 | impl<M> PartialEq for DataPayload<M> |
147 | where |
148 | M: DataMarker, |
149 | for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: PartialEq, |
150 | { |
151 | fn eq(&self, other: &Self) -> bool { |
152 | YokeTraitHack(self.get()).into_ref() == YokeTraitHack(other.get()).into_ref() |
153 | } |
154 | } |
155 | |
156 | impl<M> Eq for DataPayload<M> |
157 | where |
158 | M: DataMarker, |
159 | for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Eq, |
160 | { |
161 | } |
162 | |
163 | #[test ] |
164 | fn test_clone_eq() { |
165 | use crate::hello_world::*; |
166 | let p1: DataPayload = DataPayload::<HelloWorldV1Marker>::from_static_str("Demo" ); |
167 | #[allow (clippy::redundant_clone)] |
168 | let p2 = p1.clone(); |
169 | assert_eq!(p1, p2); |
170 | } |
171 | |
172 | impl<M> DataPayload<M> |
173 | where |
174 | M: DataMarker, |
175 | { |
176 | /// Convert a fully owned (`'static`) data struct into a DataPayload. |
177 | /// |
178 | /// This constructor creates `'static` payloads. |
179 | /// |
180 | /// # Examples |
181 | /// |
182 | /// ``` |
183 | /// use icu_provider::hello_world::*; |
184 | /// use icu_provider::prelude::*; |
185 | /// use std::borrow::Cow; |
186 | /// |
187 | /// let local_struct = HelloWorldV1 { |
188 | /// message: Cow::Owned("example" .to_owned()), |
189 | /// }; |
190 | /// |
191 | /// let payload = |
192 | /// DataPayload::<HelloWorldV1Marker>::from_owned(local_struct.clone()); |
193 | /// |
194 | /// assert_eq!(payload.get(), &local_struct); |
195 | /// ``` |
196 | #[inline ] |
197 | pub const fn from_owned(data: M::Yokeable) -> Self { |
198 | Self(DataPayloadInner::Yoke(Yoke::new_owned(data))) |
199 | } |
200 | |
201 | #[doc (hidden)] |
202 | #[inline ] |
203 | pub const fn from_static_ref(data: &'static M::Yokeable) -> Self { |
204 | Self(DataPayloadInner::StaticRef(data)) |
205 | } |
206 | |
207 | /// Convert a DataPayload that was created via [`DataPayload::from_owned()`] back into the |
208 | /// concrete type used to construct it. |
209 | pub fn try_unwrap_owned(self) -> Result<M::Yokeable, DataError> { |
210 | match self.0 { |
211 | DataPayloadInner::Yoke(yoke) => yoke.try_into_yokeable().ok(), |
212 | DataPayloadInner::StaticRef(_) => None, |
213 | } |
214 | .ok_or(DataErrorKind::InvalidState.with_str_context("try_unwrap_owned" )) |
215 | } |
216 | |
217 | /// Mutate the data contained in this DataPayload. |
218 | /// |
219 | /// For safety, all mutation operations must take place within a helper function that cannot |
220 | /// borrow data from the surrounding context. |
221 | /// |
222 | /// # Examples |
223 | /// |
224 | /// Basic usage: |
225 | /// |
226 | /// ``` |
227 | /// use icu_provider::hello_world::HelloWorldV1Marker; |
228 | /// use icu_provider::prelude::*; |
229 | /// |
230 | /// let mut payload = |
231 | /// DataPayload::<HelloWorldV1Marker>::from_static_str("Hello" ); |
232 | /// |
233 | /// payload.with_mut(|s| s.message.to_mut().push_str(" World" )); |
234 | /// |
235 | /// assert_eq!("Hello World" , payload.get().message); |
236 | /// ``` |
237 | /// |
238 | /// To transfer data from the context into the data struct, use the `move` keyword: |
239 | /// |
240 | /// ``` |
241 | /// use icu_provider::hello_world::HelloWorldV1Marker; |
242 | /// use icu_provider::prelude::*; |
243 | /// |
244 | /// let mut payload = |
245 | /// DataPayload::<HelloWorldV1Marker>::from_static_str("Hello" ); |
246 | /// |
247 | /// let suffix = " World" ; |
248 | /// payload.with_mut(move |s| s.message.to_mut().push_str(suffix)); |
249 | /// |
250 | /// assert_eq!("Hello World" , payload.get().message); |
251 | /// ``` |
252 | pub fn with_mut<'a, F>(&'a mut self, f: F) |
253 | where |
254 | F: 'static + for<'b> FnOnce(&'b mut <M::Yokeable as Yokeable<'a>>::Output), |
255 | M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>, |
256 | { |
257 | if let DataPayloadInner::StaticRef(r) = self.0 { |
258 | self.0 = DataPayloadInner::Yoke(Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))); |
259 | } |
260 | match &mut self.0 { |
261 | DataPayloadInner::Yoke(yoke) => yoke.with_mut(f), |
262 | _ => unreachable!(), |
263 | } |
264 | } |
265 | |
266 | /// Borrows the underlying data. |
267 | /// |
268 | /// This function should be used like `Deref` would normally be used. For more information on |
269 | /// why DataPayload cannot implement `Deref`, see the `yoke` crate. |
270 | /// |
271 | /// # Examples |
272 | /// |
273 | /// ``` |
274 | /// use icu_provider::hello_world::HelloWorldV1Marker; |
275 | /// use icu_provider::prelude::*; |
276 | /// |
277 | /// let payload = DataPayload::<HelloWorldV1Marker>::from_static_str("Demo" ); |
278 | /// |
279 | /// assert_eq!("Demo" , payload.get().message); |
280 | /// ``` |
281 | #[inline ] |
282 | #[allow (clippy::needless_lifetimes)] |
283 | pub fn get<'a>(&'a self) -> &'a <M::Yokeable as Yokeable<'a>>::Output { |
284 | match &self.0 { |
285 | DataPayloadInner::Yoke(yoke) => yoke.get(), |
286 | DataPayloadInner::StaticRef(r) => Yokeable::transform(*r), |
287 | } |
288 | } |
289 | |
290 | /// Maps `DataPayload<M>` to `DataPayload<M2>` by projecting it with [`Yoke::map_project`]. |
291 | /// |
292 | /// This is accomplished by a function that takes `M`'s data type and returns `M2`'s data |
293 | /// type. The function takes a second argument which should be ignored. For more details, |
294 | /// see [`Yoke::map_project()`]. |
295 | /// |
296 | /// The standard [`DataPayload::map_project()`] function moves `self` and cannot capture any |
297 | /// data from its context. Use one of the sister methods if you need these capabilities: |
298 | /// |
299 | /// - [`DataPayload::map_project_cloned()`] if you don't have ownership of `self` |
300 | /// - [`DataPayload::try_map_project()`] to bubble up an error |
301 | /// - [`DataPayload::try_map_project_cloned()`] to do both of the above |
302 | /// |
303 | /// # Examples |
304 | /// |
305 | /// Map from `HelloWorldV1` to a `Cow<str>` containing just the message: |
306 | /// |
307 | /// ``` |
308 | /// use icu_provider::hello_world::*; |
309 | /// use icu_provider::prelude::*; |
310 | /// use std::borrow::Cow; |
311 | /// |
312 | /// // A custom marker type is required when using `map_project`. The Yokeable should be the |
313 | /// // target type, and the Cart should correspond to the type being transformed. |
314 | /// |
315 | /// struct HelloWorldV1MessageMarker; |
316 | /// impl DataMarker for HelloWorldV1MessageMarker { |
317 | /// type Yokeable = Cow<'static, str>; |
318 | /// } |
319 | /// |
320 | /// let p1: DataPayload<HelloWorldV1Marker> = DataPayload::from_owned(HelloWorldV1 { |
321 | /// message: Cow::Borrowed("Hello World" ), |
322 | /// }); |
323 | /// |
324 | /// assert_eq!("Hello World" , p1.get().message); |
325 | /// |
326 | /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1.map_project(|obj, _| obj.message); |
327 | /// |
328 | /// // Note: at this point, p1 has been moved. |
329 | /// assert_eq!("Hello World" , p2.get()); |
330 | /// ``` |
331 | #[allow (clippy::type_complexity)] |
332 | pub fn map_project<M2, F>(self, f: F) -> DataPayload<M2> |
333 | where |
334 | M2: DataMarker, |
335 | F: for<'a> FnOnce( |
336 | <M::Yokeable as Yokeable<'a>>::Output, |
337 | PhantomData<&'a ()>, |
338 | ) -> <M2::Yokeable as Yokeable<'a>>::Output, |
339 | M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>, |
340 | { |
341 | DataPayload(DataPayloadInner::Yoke( |
342 | match self.0 { |
343 | DataPayloadInner::Yoke(yoke) => yoke, |
344 | DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)), |
345 | } |
346 | .map_project(f), |
347 | )) |
348 | } |
349 | |
350 | /// Version of [`DataPayload::map_project()`] that borrows `self` instead of moving `self`. |
351 | /// |
352 | /// # Examples |
353 | /// |
354 | /// Same example as above, but this time, do not move out of `p1`: |
355 | /// |
356 | /// ``` |
357 | /// // Same imports and definitions as above |
358 | /// # use icu_provider::hello_world::*; |
359 | /// # use icu_provider::prelude::*; |
360 | /// # use std::borrow::Cow; |
361 | /// # struct HelloWorldV1MessageMarker; |
362 | /// # impl DataMarker for HelloWorldV1MessageMarker { |
363 | /// # type Yokeable = Cow<'static, str>; |
364 | /// # } |
365 | /// |
366 | /// let p1: DataPayload<HelloWorldV1Marker> = |
367 | /// DataPayload::from_owned(HelloWorldV1 { |
368 | /// message: Cow::Borrowed("Hello World" ), |
369 | /// }); |
370 | /// |
371 | /// assert_eq!("Hello World" , p1.get().message); |
372 | /// |
373 | /// let p2: DataPayload<HelloWorldV1MessageMarker> = |
374 | /// p1.map_project_cloned(|obj, _| obj.message.clone()); |
375 | /// |
376 | /// // Note: p1 is still valid. |
377 | /// assert_eq!(p1.get().message, *p2.get()); |
378 | /// ``` |
379 | #[allow (clippy::type_complexity)] |
380 | pub fn map_project_cloned<'this, M2, F>(&'this self, f: F) -> DataPayload<M2> |
381 | where |
382 | M2: DataMarker, |
383 | F: for<'a> FnOnce( |
384 | &'this <M::Yokeable as Yokeable<'a>>::Output, |
385 | PhantomData<&'a ()>, |
386 | ) -> <M2::Yokeable as Yokeable<'a>>::Output, |
387 | { |
388 | DataPayload(DataPayloadInner::Yoke(match &self.0 { |
389 | DataPayloadInner::Yoke(yoke) => yoke.map_project_cloned(f), |
390 | DataPayloadInner::StaticRef(r) => { |
391 | let output: <M2::Yokeable as Yokeable<'static>>::Output = |
392 | f(Yokeable::transform(*r), PhantomData); |
393 | // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable |
394 | let yokeable: M2::Yokeable = unsafe { M2::Yokeable::make(output) }; |
395 | Yoke::new_owned(yokeable) |
396 | } |
397 | })) |
398 | } |
399 | |
400 | /// Version of [`DataPayload::map_project()`] that bubbles up an error from `f`. |
401 | /// |
402 | /// # Examples |
403 | /// |
404 | /// Same example as above, but bubble up an error: |
405 | /// |
406 | /// ``` |
407 | /// // Same imports and definitions as above |
408 | /// # use icu_provider::hello_world::*; |
409 | /// # use icu_provider::prelude::*; |
410 | /// # use std::borrow::Cow; |
411 | /// # struct HelloWorldV1MessageMarker; |
412 | /// # impl DataMarker for HelloWorldV1MessageMarker { |
413 | /// # type Yokeable = Cow<'static, str>; |
414 | /// # } |
415 | /// |
416 | /// let p1: DataPayload<HelloWorldV1Marker> = |
417 | /// DataPayload::from_owned(HelloWorldV1 { |
418 | /// message: Cow::Borrowed("Hello World" ), |
419 | /// }); |
420 | /// |
421 | /// assert_eq!("Hello World" , p1.get().message); |
422 | /// |
423 | /// let string_to_append = "Extra" ; |
424 | /// let p2: DataPayload<HelloWorldV1MessageMarker> = |
425 | /// p1.try_map_project(|mut obj, _| { |
426 | /// if obj.message.is_empty() { |
427 | /// return Err("Example error" ); |
428 | /// } |
429 | /// obj.message.to_mut().push_str(string_to_append); |
430 | /// Ok(obj.message) |
431 | /// })?; |
432 | /// |
433 | /// assert_eq!("Hello WorldExtra" , p2.get()); |
434 | /// # Ok::<(), &'static str>(()) |
435 | /// ``` |
436 | #[allow (clippy::type_complexity)] |
437 | pub fn try_map_project<M2, F, E>(self, f: F) -> Result<DataPayload<M2>, E> |
438 | where |
439 | M2: DataMarker, |
440 | F: for<'a> FnOnce( |
441 | <M::Yokeable as Yokeable<'a>>::Output, |
442 | PhantomData<&'a ()>, |
443 | ) -> Result<<M2::Yokeable as Yokeable<'a>>::Output, E>, |
444 | M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>, |
445 | { |
446 | Ok(DataPayload(DataPayloadInner::Yoke( |
447 | match self.0 { |
448 | DataPayloadInner::Yoke(yoke) => yoke, |
449 | DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)), |
450 | } |
451 | .try_map_project(f)?, |
452 | ))) |
453 | } |
454 | |
455 | /// Version of [`DataPayload::map_project_cloned()`] that bubbles up an error from `f`. |
456 | /// |
457 | /// # Examples |
458 | /// |
459 | /// Same example as above, but bubble up an error: |
460 | /// |
461 | /// ``` |
462 | /// // Same imports and definitions as above |
463 | /// # use icu_provider::hello_world::*; |
464 | /// # use icu_provider::prelude::*; |
465 | /// # use std::borrow::Cow; |
466 | /// # struct HelloWorldV1MessageMarker; |
467 | /// # impl DataMarker for HelloWorldV1MessageMarker { |
468 | /// # type Yokeable = Cow<'static, str>; |
469 | /// # } |
470 | /// |
471 | /// let p1: DataPayload<HelloWorldV1Marker> = |
472 | /// DataPayload::from_owned(HelloWorldV1 { |
473 | /// message: Cow::Borrowed("Hello World" ), |
474 | /// }); |
475 | /// |
476 | /// assert_eq!("Hello World" , p1.get().message); |
477 | /// |
478 | /// let string_to_append = "Extra" ; |
479 | /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1 |
480 | /// .try_map_project_cloned(|obj, _| { |
481 | /// if obj.message.is_empty() { |
482 | /// return Err("Example error" ); |
483 | /// } |
484 | /// let mut message = obj.message.clone(); |
485 | /// message.to_mut().push_str(string_to_append); |
486 | /// Ok(message) |
487 | /// })?; |
488 | /// |
489 | /// // Note: p1 is still valid, but the values no longer equal. |
490 | /// assert_ne!(p1.get().message, *p2.get()); |
491 | /// assert_eq!("Hello WorldExtra" , p2.get()); |
492 | /// # Ok::<(), &'static str>(()) |
493 | /// ``` |
494 | #[allow (clippy::type_complexity)] |
495 | pub fn try_map_project_cloned<'this, M2, F, E>(&'this self, f: F) -> Result<DataPayload<M2>, E> |
496 | where |
497 | M2: DataMarker, |
498 | F: for<'a> FnOnce( |
499 | &'this <M::Yokeable as Yokeable<'a>>::Output, |
500 | PhantomData<&'a ()>, |
501 | ) -> Result<<M2::Yokeable as Yokeable<'a>>::Output, E>, |
502 | { |
503 | Ok(DataPayload(DataPayloadInner::Yoke(match &self.0 { |
504 | DataPayloadInner::Yoke(yoke) => yoke.try_map_project_cloned(f)?, |
505 | DataPayloadInner::StaticRef(r) => { |
506 | let output: <M2::Yokeable as Yokeable<'static>>::Output = |
507 | f(Yokeable::transform(*r), PhantomData)?; |
508 | // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable |
509 | Yoke::new_owned(unsafe { M2::Yokeable::make(output) }) |
510 | } |
511 | }))) |
512 | } |
513 | |
514 | /// Convert between two [`DataMarker`] types that are compatible with each other |
515 | /// with compile-time type checking. |
516 | /// |
517 | /// This happens if they both have the same [`DataMarker::Yokeable`] type. |
518 | /// |
519 | /// Can be used to erase the key of a data payload in cases where multiple keys correspond |
520 | /// to the same data struct. |
521 | /// |
522 | /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`]. |
523 | /// |
524 | /// # Examples |
525 | /// |
526 | /// ```no_run |
527 | /// use icu_locid::locale; |
528 | /// use icu_provider::hello_world::*; |
529 | /// use icu_provider::prelude::*; |
530 | /// |
531 | /// struct CustomHelloWorldV1Marker; |
532 | /// impl DataMarker for CustomHelloWorldV1Marker { |
533 | /// type Yokeable = HelloWorldV1<'static>; |
534 | /// } |
535 | /// |
536 | /// let hello_world: DataPayload<HelloWorldV1Marker> = todo!(); |
537 | /// let custom: DataPayload<CustomHelloWorldV1Marker> = hello_world.cast(); |
538 | /// ``` |
539 | #[inline ] |
540 | pub fn cast<M2>(self) -> DataPayload<M2> |
541 | where |
542 | M2: DataMarker<Yokeable = M::Yokeable>, |
543 | { |
544 | DataPayload(match self.0 { |
545 | DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke), |
546 | DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(r), |
547 | }) |
548 | } |
549 | |
550 | /// Convert a mutable reference of a [`DataPayload`] to another mutable reference |
551 | /// of the same type with runtime type checking. |
552 | /// |
553 | /// Primarily useful to convert from a generic to a concrete marker type. |
554 | /// |
555 | /// If the `M2` type argument does not match the true marker type, a `DataError` is returned. |
556 | /// |
557 | /// For compile-time static casting, use [`DataPayload::cast()`]. |
558 | /// |
559 | /// # Examples |
560 | /// |
561 | /// Change the results of a particular request based on key: |
562 | /// |
563 | /// ``` |
564 | /// use icu_locid::locale; |
565 | /// use icu_provider::hello_world::*; |
566 | /// use icu_provider::prelude::*; |
567 | /// |
568 | /// struct MyWrapper<P> { |
569 | /// inner: P, |
570 | /// } |
571 | /// |
572 | /// impl<M, P> DataProvider<M> for MyWrapper<P> |
573 | /// where |
574 | /// M: KeyedDataMarker, |
575 | /// P: DataProvider<M>, |
576 | /// { |
577 | /// #[inline ] |
578 | /// fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { |
579 | /// let mut res = self.inner.load(req)?; |
580 | /// if let Some(ref mut generic_payload) = res.payload { |
581 | /// let mut cast_result = |
582 | /// generic_payload.dynamic_cast_mut::<HelloWorldV1Marker>(); |
583 | /// if let Ok(ref mut concrete_payload) = cast_result { |
584 | /// // Add an emoji to the hello world message |
585 | /// concrete_payload.with_mut(|data| { |
586 | /// data.message.to_mut().insert_str(0, "✨ " ); |
587 | /// }); |
588 | /// } |
589 | /// } |
590 | /// Ok(res) |
591 | /// } |
592 | /// } |
593 | /// |
594 | /// let provider = MyWrapper { |
595 | /// inner: HelloWorldProvider, |
596 | /// }; |
597 | /// let formatter = |
598 | /// HelloWorldFormatter::try_new_unstable(&provider, &locale!("de" ).into()) |
599 | /// .unwrap(); |
600 | /// |
601 | /// assert_eq!(formatter.format_to_string(), "✨ Hallo Welt" ); |
602 | /// ``` |
603 | #[inline ] |
604 | pub fn dynamic_cast_mut<M2>(&mut self) -> Result<&mut DataPayload<M2>, DataError> |
605 | where |
606 | M2: DataMarker, |
607 | { |
608 | let this: &mut dyn core::any::Any = self; |
609 | if let Some(this) = this.downcast_mut() { |
610 | Ok(this) |
611 | } else { |
612 | Err(DataError::for_type::<M2>().with_str_context(core::any::type_name::<M>())) |
613 | } |
614 | } |
615 | } |
616 | |
617 | impl DataPayload<BufferMarker> { |
618 | /// Converts an owned byte buffer into a `DataPayload<BufferMarker>`. |
619 | pub fn from_owned_buffer(buffer: Box<[u8]>) -> Self { |
620 | let yoke: Yoke<&[u8], Rc>> = Yoke::attach_to_cart(cart:SelectedRc::new(buffer), |b: &Box<[u8]>| &**b); |
621 | // Safe because cart is wrapped |
622 | let yoke: Yoke<&[u8], Option> = unsafe { yoke.replace_cart(|b: Rc>| Some(Cart(b))) }; |
623 | Self(DataPayloadInner::Yoke(yoke)) |
624 | } |
625 | |
626 | /// Converts a yoked byte buffer into a `DataPayload<BufferMarker>`. |
627 | pub fn from_yoked_buffer(yoke: Yoke<&'static [u8], Option<Cart>>) -> Self { |
628 | Self(DataPayloadInner::Yoke(yoke)) |
629 | } |
630 | |
631 | /// Converts a static byte buffer into a `DataPayload<BufferMarker>`. |
632 | pub fn from_static_buffer(buffer: &'static [u8]) -> Self { |
633 | Self(DataPayloadInner::Yoke(Yoke::new_owned(yokeable:buffer))) |
634 | } |
635 | } |
636 | |
637 | impl<M> Default for DataPayload<M> |
638 | where |
639 | M: DataMarker, |
640 | M::Yokeable: Default, |
641 | { |
642 | fn default() -> Self { |
643 | Self::from_owned(data:Default::default()) |
644 | } |
645 | } |
646 | |
647 | /// A response object containing an object as payload and metadata about it. |
648 | #[allow (clippy::exhaustive_structs)] // this type is stable |
649 | pub struct DataResponse<M> |
650 | where |
651 | M: DataMarker, |
652 | { |
653 | /// Metadata about the returned object. |
654 | pub metadata: DataResponseMetadata, |
655 | |
656 | /// The object itself; `None` if it was not loaded. |
657 | pub payload: Option<DataPayload<M>>, |
658 | } |
659 | |
660 | impl<M> DataResponse<M> |
661 | where |
662 | M: DataMarker, |
663 | { |
664 | /// Takes ownership of the underlying payload. Error if not present. |
665 | /// |
666 | /// To take the metadata, too, use [`Self::take_metadata_and_payload()`]. |
667 | #[inline ] |
668 | pub fn take_payload(self) -> Result<DataPayload<M>, DataError> { |
669 | Ok(self.take_metadata_and_payload()?.1) |
670 | } |
671 | |
672 | /// Takes ownership of the underlying metadata and payload. Error if payload is not present. |
673 | #[inline ] |
674 | pub fn take_metadata_and_payload( |
675 | self, |
676 | ) -> Result<(DataResponseMetadata, DataPayload<M>), DataError> { |
677 | Ok(( |
678 | self.metadata, |
679 | self.payload |
680 | .ok_or_else(|| DataErrorKind::MissingPayload.with_type_context::<M>())?, |
681 | )) |
682 | } |
683 | } |
684 | |
685 | impl<M> TryFrom<DataResponse<M>> for DataPayload<M> |
686 | where |
687 | M: DataMarker, |
688 | { |
689 | type Error = DataError; |
690 | |
691 | fn try_from(response: DataResponse<M>) -> Result<Self, Self::Error> { |
692 | response.take_payload() |
693 | } |
694 | } |
695 | |
696 | impl<M> Debug for DataResponse<M> |
697 | where |
698 | M: DataMarker, |
699 | for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug, |
700 | { |
701 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
702 | write!( |
703 | f, |
704 | "DataResponse {{ metadata: {:?}, payload: {:?} }}" , |
705 | self.metadata, self.payload |
706 | ) |
707 | } |
708 | } |
709 | |
710 | /// Cloning a DataResponse is generally a cheap operation. |
711 | /// See notes in the `Clone` impl for [`Yoke`]. |
712 | /// |
713 | /// # Examples |
714 | /// |
715 | /// ```no_run |
716 | /// use icu_provider::hello_world::*; |
717 | /// use icu_provider::prelude::*; |
718 | /// |
719 | /// let resp1: DataResponse<HelloWorldV1Marker> = todo!(); |
720 | /// let resp2 = resp1.clone(); |
721 | /// ``` |
722 | impl<M> Clone for DataResponse<M> |
723 | where |
724 | M: DataMarker, |
725 | for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone, |
726 | { |
727 | fn clone(&self) -> Self { |
728 | Self { |
729 | metadata: self.metadata.clone(), |
730 | payload: self.payload.clone(), |
731 | } |
732 | } |
733 | } |
734 | |
735 | #[test ] |
736 | fn test_debug() { |
737 | use crate::hello_world::*; |
738 | use alloc::borrow::Cow; |
739 | let resp: DataResponse = DataResponse::<HelloWorldV1Marker> { |
740 | metadata: Default::default(), |
741 | payload: Some(DataPayload::from_owned(data:HelloWorldV1 { |
742 | message: Cow::Borrowed("foo" ), |
743 | })), |
744 | }; |
745 | assert_eq!("DataResponse { metadata: DataResponseMetadata { locale: None, buffer_format: None }, payload: Some(HelloWorldV1 { message: \"foo \" }) }" , format!("{resp:?}" )); |
746 | } |
747 | |