icu_provider/response.rs
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
5use crate::buf::BufferMarker;
6use crate::error::{DataError, DataErrorKind};
7use crate::marker::DataMarker;
8use crate::request::DataLocale;
9use alloc::boxed::Box;
10use core::convert::TryFrom;
11use core::fmt::Debug;
12use core::marker::PhantomData;
13use core::ops::Deref;
14use yoke::cartable_ptr::CartableOptionPointer;
15use yoke::trait_hack::YokeTraitHack;
16use yoke::*;
17
18#[cfg(not(feature = "sync"))]
19use alloc::rc::Rc as SelectedRc;
20#[cfg(feature = "sync")]
21use alloc::sync::Arc as SelectedRc;
22
23/// A response object containing metadata about the returned data.
24#[derive(Debug, Clone, PartialEq, Default)]
25#[non_exhaustive]
26pub struct DataResponseMetadata {
27 /// The resolved locale of the returned data, if locale fallbacking was performed.
28 pub locale: Option<DataLocale>,
29 /// The format of the buffer for buffer-backed data, if known (for example, JSON).
30 pub buffer_format: Option<crate::buf::BufferFormat>,
31}
32
33/// A container for data payloads returned from a data provider.
34///
35/// [`DataPayload`] is built on top of the [`yoke`] framework, which allows for cheap, zero-copy
36/// operations on data via the use of self-references.
37///
38/// The type of the data stored in [`DataPayload`] is determined by the [`DataMarker`] type parameter.
39///
40/// ## Accessing the data
41///
42/// To get a reference to the data inside [`DataPayload`], use [`DataPayload::get()`]. If you need
43/// to store the data for later use, you need to store the [`DataPayload`] itself, since `get` only
44/// returns a reference with an ephemeral lifetime.
45///
46/// ## Mutating the data
47///
48/// To modify the data stored in a [`DataPayload`], use [`DataPayload::with_mut()`].
49///
50/// ## Transforming the data to a different type
51///
52/// To transform a [`DataPayload`] to a different type backed by the same data store (cart), use
53/// [`DataPayload::map_project()`] or one of its sister methods.
54///
55/// # Cargo feature: `sync`
56///
57/// By default, the payload uses non-concurrent reference counting internally, and hence is neither
58/// [`Sync`] nor [`Send`]; if these traits are required, the `sync` Cargo feature can be enabled.
59///
60/// # Examples
61///
62/// Basic usage, using the `HelloWorldV1Marker` marker:
63///
64/// ```
65/// use icu_provider::hello_world::*;
66/// use icu_provider::prelude::*;
67/// use std::borrow::Cow;
68///
69/// let payload = DataPayload::<HelloWorldV1Marker>::from_owned(HelloWorldV1 {
70/// message: Cow::Borrowed("Demo"),
71/// });
72///
73/// assert_eq!("Demo", payload.get().message);
74/// ```
75pub struct DataPayload<M: DataMarker>(pub(crate) DataPayloadInner<M>);
76
77/// A container for data payloads with storage for something else.
78///
79/// The type parameter `O` is stored as part of the interior enum, leading to
80/// better stack size optimization. `O` can be as large as the [`DataPayload`]
81/// minus two words without impacting stack size.
82///
83/// # Examples
84///
85/// Create and use DataPayloadOr:
86///
87/// ```
88/// use icu_provider::hello_world::*;
89/// use icu_provider::prelude::*;
90/// use icu_provider::DataPayloadOr;
91///
92/// let payload: DataPayload<HelloWorldV1Marker> = HelloWorldProvider
93/// .load(DataRequest {
94/// locale: &"de".parse().unwrap(),
95/// metadata: Default::default(),
96/// })
97/// .expect("Loading should succeed")
98/// .take_payload()
99/// .expect("Data should be present");
100///
101/// let payload_some =
102/// DataPayloadOr::<HelloWorldV1Marker, ()>::from_payload(payload);
103/// let payload_none = DataPayloadOr::<HelloWorldV1Marker, ()>::from_other(());
104///
105/// assert_eq!(
106/// payload_some.get(),
107/// Ok(&HelloWorldV1 {
108/// message: "Hallo Welt".into()
109/// })
110/// );
111/// assert_eq!(payload_none.get(), Err(&()));
112/// ```
113///
114/// Stack size comparison:
115///
116/// ```
117/// use core::mem::size_of;
118/// use icu_provider::prelude::*;
119/// use icu_provider::DataPayloadOr;
120///
121/// const W: usize = size_of::<usize>();
122///
123/// // SampleStruct is 3 words:
124/// # #[icu_provider::data_struct(SampleStructMarker)]
125/// # pub struct SampleStruct<'data>(usize, usize, &'data ());
126/// assert_eq!(W * 3, size_of::<SampleStruct>());
127///
128/// // DataPayload adds a word for a total of 4 words:
129/// assert_eq!(W * 4, size_of::<DataPayload<SampleStructMarker>>());
130///
131/// // Option<DataPayload> balloons to 5 words:
132/// assert_eq!(W * 5, size_of::<Option<DataPayload<SampleStructMarker>>>());
133///
134/// // But, using DataPayloadOr is the same size as DataPayload:
135/// assert_eq!(W * 4, size_of::<DataPayloadOr<SampleStructMarker, ()>>());
136///
137/// // The largest optimized Other type is two words smaller than the DataPayload:
138/// assert_eq!(W * 4, size_of::<DataPayloadOr<SampleStructMarker, [usize; 1]>>());
139/// assert_eq!(W * 4, size_of::<DataPayloadOr<SampleStructMarker, [usize; 2]>>());
140/// assert_eq!(W * 5, size_of::<DataPayloadOr<SampleStructMarker, [usize; 3]>>());
141/// ```
142#[doc(hidden)] // TODO(#4467): establish this as an internal API
143pub struct DataPayloadOr<M: DataMarker, O>(pub(crate) DataPayloadOrInner<M, O>);
144
145pub(crate) enum DataPayloadInner<M: DataMarker> {
146 Yoke(Yoke<M::Yokeable, CartableOptionPointer<CartInner>>),
147 StaticRef(&'static M::Yokeable),
148}
149
150pub(crate) enum DataPayloadOrInner<M: DataMarker, O> {
151 Yoke(Yoke<M::Yokeable, CartableOptionPointer<CartInner>>),
152 Inner(DataPayloadOrInnerInner<M, O>),
153}
154
155pub(crate) enum DataPayloadOrInnerInner<M: DataMarker, O> {
156 StaticRef(&'static M::Yokeable),
157 Other(O),
158}
159
160/// The type of the "cart" that is used by [`DataPayload`].
161///
162/// This type is public but the inner cart type is private. To create a
163/// [`Yoke`] with this cart, use [`Cart::try_make_yoke`]. Then, convert
164/// it to a [`DataPayload`] with [`DataPayload::from_yoked_buffer`].
165#[derive(Clone, Debug)]
166#[allow(clippy::redundant_allocation)] // false positive, it's cheaper to wrap an existing Box in an Rc than to reallocate a huge Rc
167pub struct Cart(CartInner);
168
169/// The actual cart type (private typedef).
170pub(crate) type CartInner = SelectedRc<Box<[u8]>>;
171
172impl Deref for Cart {
173 type Target = Box<[u8]>;
174 fn deref(&self) -> &Self::Target {
175 &self.0
176 }
177}
178// Safe because both Rc and Arc are StableDeref, and our impl delegates.
179unsafe impl stable_deref_trait::StableDeref for Cart {}
180// Safe because both Rc and Arc are CloneableCart, and our impl delegates.
181unsafe impl yoke::CloneableCart for Cart {}
182
183impl Cart {
184 /// Creates a `Yoke<Y, Option<Cart>>` from owned bytes by applying `f`.
185 pub fn try_make_yoke<Y, F, E>(cart: Box<[u8]>, f: F) -> Result<Yoke<Y, Option<Self>>, E>
186 where
187 for<'a> Y: Yokeable<'a>,
188 F: FnOnce(&[u8]) -> Result<<Y as Yokeable>::Output, E>,
189 {
190 Yoke::try_attach_to_cart(SelectedRc::new(cart), |b| f(b))
191 // Safe because the cart is only wrapped
192 .map(|yoke| unsafe { yoke.replace_cart(Cart) })
193 .map(Yoke::wrap_cart_in_option)
194 }
195
196 /// Helper function to convert `Yoke<Y, Option<Cart>>` to `Yoke<Y, Option<CartInner>>`.
197 #[inline]
198 pub(crate) fn unwrap_cart<Y>(yoke: Yoke<Y, Option<Cart>>) -> Yoke<Y, Option<CartInner>>
199 where
200 for<'a> Y: Yokeable<'a>,
201 {
202 // Safety: `Cart` has one field and we are removing it from the newtype,
203 // and we are preserving it in the new cart, unwrapping it from the newtype.
204 unsafe { yoke.replace_cart(|option_cart| option_cart.map(|cart| cart.0)) }
205 }
206}
207
208impl<M> Debug for DataPayload<M>
209where
210 M: DataMarker,
211 for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug,
212{
213 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
214 self.get().fmt(f)
215 }
216}
217
218impl<M, O> Debug for DataPayloadOr<M, O>
219where
220 M: DataMarker,
221 for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug,
222 O: Debug,
223{
224 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
225 self.get()
226 .map(|v| Debug::fmt(&v, f))
227 .unwrap_or_else(|v| Debug::fmt(v, f))
228 }
229}
230
231/// Cloning a DataPayload is generally a cheap operation.
232/// See notes in the `Clone` impl for [`Yoke`].
233///
234/// # Examples
235///
236/// ```no_run
237/// use icu_provider::hello_world::*;
238/// use icu_provider::prelude::*;
239///
240/// let resp1: DataPayload<HelloWorldV1Marker> = todo!();
241/// let resp2 = resp1.clone();
242/// ```
243impl<M> Clone for DataPayload<M>
244where
245 M: DataMarker,
246 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
247{
248 fn clone(&self) -> Self {
249 Self(match &self.0 {
250 DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke.clone()),
251 DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(*r),
252 })
253 }
254}
255
256impl<M, O> Clone for DataPayloadOr<M, O>
257where
258 M: DataMarker,
259 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
260 O: Clone,
261{
262 fn clone(&self) -> Self {
263 Self(match &self.0 {
264 DataPayloadOrInner::Yoke(yoke) => DataPayloadOrInner::Yoke(yoke.clone()),
265 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(r)) => {
266 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(*r))
267 }
268 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o)) => {
269 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o.clone()))
270 }
271 })
272 }
273}
274
275impl<M> PartialEq for DataPayload<M>
276where
277 M: DataMarker,
278 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: PartialEq,
279{
280 fn eq(&self, other: &Self) -> bool {
281 YokeTraitHack(self.get()).into_ref() == YokeTraitHack(other.get()).into_ref()
282 }
283}
284
285impl<M, O> PartialEq for DataPayloadOr<M, O>
286where
287 M: DataMarker,
288 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: PartialEq,
289 O: Eq,
290{
291 fn eq(&self, other: &Self) -> bool {
292 match (self.get(), other.get()) {
293 (Ok(x), Ok(y)) => YokeTraitHack(x).into_ref() == YokeTraitHack(y).into_ref(),
294 (Err(x), Err(y)) => x == y,
295 _ => false,
296 }
297 }
298}
299
300impl<M> Eq for DataPayload<M>
301where
302 M: DataMarker,
303 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Eq,
304{
305}
306
307impl<M, O> Eq for DataPayloadOr<M, O>
308where
309 M: DataMarker,
310 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Eq,
311 O: Eq,
312{
313}
314
315#[test]
316fn test_clone_eq() {
317 use crate::hello_world::*;
318 let p1 = DataPayload::<HelloWorldV1Marker>::from_static_str("Demo");
319 #[allow(clippy::redundant_clone)]
320 let p2 = p1.clone();
321 assert_eq!(p1, p2);
322
323 let p1 = DataPayloadOr::<HelloWorldV1Marker, usize>::from_payload(p1);
324 #[allow(clippy::redundant_clone)]
325 let p2 = p1.clone();
326 assert_eq!(p1, p2);
327
328 let p3 = DataPayloadOr::<HelloWorldV1Marker, usize>::from_other(555);
329 #[allow(clippy::redundant_clone)]
330 let p4 = p3.clone();
331 assert_eq!(p3, p4);
332
333 let p5 = DataPayloadOr::<HelloWorldV1Marker, usize>::from_other(666);
334 assert_ne!(p3, p5);
335 assert_ne!(p4, p5);
336
337 assert_ne!(p1, p3);
338 assert_ne!(p1, p4);
339 assert_ne!(p1, p5);
340 assert_ne!(p2, p3);
341 assert_ne!(p2, p4);
342 assert_ne!(p2, p5);
343}
344
345impl<M> DataPayload<M>
346where
347 M: DataMarker,
348{
349 /// Convert a fully owned (`'static`) data struct into a DataPayload.
350 ///
351 /// This constructor creates `'static` payloads.
352 ///
353 /// # Examples
354 ///
355 /// ```
356 /// use icu_provider::hello_world::*;
357 /// use icu_provider::prelude::*;
358 /// use std::borrow::Cow;
359 ///
360 /// let local_struct = HelloWorldV1 {
361 /// message: Cow::Owned("example".to_owned()),
362 /// };
363 ///
364 /// let payload =
365 /// DataPayload::<HelloWorldV1Marker>::from_owned(local_struct.clone());
366 ///
367 /// assert_eq!(payload.get(), &local_struct);
368 /// ```
369 #[inline]
370 pub fn from_owned(data: M::Yokeable) -> Self {
371 Self(DataPayloadInner::Yoke(
372 Yoke::new_owned(data).convert_cart_into_option_pointer(),
373 ))
374 }
375
376 #[doc(hidden)]
377 #[inline]
378 pub const fn from_static_ref(data: &'static M::Yokeable) -> Self {
379 Self(DataPayloadInner::StaticRef(data))
380 }
381
382 /// Convert a DataPayload that was created via [`DataPayload::from_owned()`] back into the
383 /// concrete type used to construct it.
384 pub fn try_unwrap_owned(self) -> Result<M::Yokeable, DataError> {
385 match self.0 {
386 DataPayloadInner::Yoke(yoke) => yoke.try_into_yokeable().ok(),
387 DataPayloadInner::StaticRef(_) => None,
388 }
389 .ok_or(DataErrorKind::InvalidState.with_str_context("try_unwrap_owned"))
390 }
391
392 /// Mutate the data contained in this DataPayload.
393 ///
394 /// For safety, all mutation operations must take place within a helper function that cannot
395 /// borrow data from the surrounding context.
396 ///
397 /// # Examples
398 ///
399 /// Basic usage:
400 ///
401 /// ```
402 /// use icu_provider::hello_world::HelloWorldV1Marker;
403 /// use icu_provider::prelude::*;
404 ///
405 /// let mut payload =
406 /// DataPayload::<HelloWorldV1Marker>::from_static_str("Hello");
407 ///
408 /// payload.with_mut(|s| s.message.to_mut().push_str(" World"));
409 ///
410 /// assert_eq!("Hello World", payload.get().message);
411 /// ```
412 ///
413 /// To transfer data from the context into the data struct, use the `move` keyword:
414 ///
415 /// ```
416 /// use icu_provider::hello_world::HelloWorldV1Marker;
417 /// use icu_provider::prelude::*;
418 ///
419 /// let mut payload =
420 /// DataPayload::<HelloWorldV1Marker>::from_static_str("Hello");
421 ///
422 /// let suffix = " World";
423 /// payload.with_mut(move |s| s.message.to_mut().push_str(suffix));
424 ///
425 /// assert_eq!("Hello World", payload.get().message);
426 /// ```
427 pub fn with_mut<'a, F>(&'a mut self, f: F)
428 where
429 F: 'static + for<'b> FnOnce(&'b mut <M::Yokeable as Yokeable<'a>>::Output),
430 M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>,
431 {
432 if let DataPayloadInner::StaticRef(r) = self.0 {
433 self.0 = DataPayloadInner::Yoke(
434 Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))
435 .convert_cart_into_option_pointer(),
436 );
437 }
438 match &mut self.0 {
439 DataPayloadInner::Yoke(yoke) => yoke.with_mut(f),
440 _ => unreachable!(),
441 }
442 }
443
444 /// Borrows the underlying data.
445 ///
446 /// This function should be used like `Deref` would normally be used. For more information on
447 /// why DataPayload cannot implement `Deref`, see the `yoke` crate.
448 ///
449 /// # Examples
450 ///
451 /// ```
452 /// use icu_provider::hello_world::HelloWorldV1Marker;
453 /// use icu_provider::prelude::*;
454 ///
455 /// let payload = DataPayload::<HelloWorldV1Marker>::from_static_str("Demo");
456 ///
457 /// assert_eq!("Demo", payload.get().message);
458 /// ```
459 #[inline]
460 #[allow(clippy::needless_lifetimes)]
461 pub fn get<'a>(&'a self) -> &'a <M::Yokeable as Yokeable<'a>>::Output {
462 match &self.0 {
463 DataPayloadInner::Yoke(yoke) => yoke.get(),
464 DataPayloadInner::StaticRef(r) => Yokeable::transform(*r),
465 }
466 }
467
468 /// Maps `DataPayload<M>` to `DataPayload<M2>` by projecting it with [`Yoke::map_project`].
469 ///
470 /// This is accomplished by a function that takes `M`'s data type and returns `M2`'s data
471 /// type. The function takes a second argument which should be ignored. For more details,
472 /// see [`Yoke::map_project()`].
473 ///
474 /// The standard [`DataPayload::map_project()`] function moves `self` and cannot capture any
475 /// data from its context. Use one of the sister methods if you need these capabilities:
476 ///
477 /// - [`DataPayload::map_project_cloned()`] if you don't have ownership of `self`
478 /// - [`DataPayload::try_map_project()`] to bubble up an error
479 /// - [`DataPayload::try_map_project_cloned()`] to do both of the above
480 ///
481 /// # Examples
482 ///
483 /// Map from `HelloWorldV1` to a `Cow<str>` containing just the message:
484 ///
485 /// ```
486 /// use icu_provider::hello_world::*;
487 /// use icu_provider::prelude::*;
488 /// use std::borrow::Cow;
489 ///
490 /// // A custom marker type is required when using `map_project`. The Yokeable should be the
491 /// // target type, and the Cart should correspond to the type being transformed.
492 ///
493 /// struct HelloWorldV1MessageMarker;
494 /// impl DataMarker for HelloWorldV1MessageMarker {
495 /// type Yokeable = Cow<'static, str>;
496 /// }
497 ///
498 /// let p1: DataPayload<HelloWorldV1Marker> = DataPayload::from_owned(HelloWorldV1 {
499 /// message: Cow::Borrowed("Hello World"),
500 /// });
501 ///
502 /// assert_eq!("Hello World", p1.get().message);
503 ///
504 /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1.map_project(|obj, _| obj.message);
505 ///
506 /// // Note: at this point, p1 has been moved.
507 /// assert_eq!("Hello World", p2.get());
508 /// ```
509 #[allow(clippy::type_complexity)]
510 pub fn map_project<M2, F>(self, f: F) -> DataPayload<M2>
511 where
512 M2: DataMarker,
513 F: for<'a> FnOnce(
514 <M::Yokeable as Yokeable<'a>>::Output,
515 PhantomData<&'a ()>,
516 ) -> <M2::Yokeable as Yokeable<'a>>::Output,
517 M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>,
518 {
519 DataPayload(DataPayloadInner::Yoke(
520 match self.0 {
521 DataPayloadInner::Yoke(yoke) => yoke,
522 DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))
523 .convert_cart_into_option_pointer(),
524 }
525 .map_project(f),
526 ))
527 }
528
529 /// Version of [`DataPayload::map_project()`] that borrows `self` instead of moving `self`.
530 ///
531 /// # Examples
532 ///
533 /// Same example as above, but this time, do not move out of `p1`:
534 ///
535 /// ```
536 /// // Same imports and definitions as above
537 /// # use icu_provider::hello_world::*;
538 /// # use icu_provider::prelude::*;
539 /// # use std::borrow::Cow;
540 /// # struct HelloWorldV1MessageMarker;
541 /// # impl DataMarker for HelloWorldV1MessageMarker {
542 /// # type Yokeable = Cow<'static, str>;
543 /// # }
544 ///
545 /// let p1: DataPayload<HelloWorldV1Marker> =
546 /// DataPayload::from_owned(HelloWorldV1 {
547 /// message: Cow::Borrowed("Hello World"),
548 /// });
549 ///
550 /// assert_eq!("Hello World", p1.get().message);
551 ///
552 /// let p2: DataPayload<HelloWorldV1MessageMarker> =
553 /// p1.map_project_cloned(|obj, _| obj.message.clone());
554 ///
555 /// // Note: p1 is still valid.
556 /// assert_eq!(p1.get().message, *p2.get());
557 /// ```
558 #[allow(clippy::type_complexity)]
559 pub fn map_project_cloned<'this, M2, F>(&'this self, f: F) -> DataPayload<M2>
560 where
561 M2: DataMarker,
562 F: for<'a> FnOnce(
563 &'this <M::Yokeable as Yokeable<'a>>::Output,
564 PhantomData<&'a ()>,
565 ) -> <M2::Yokeable as Yokeable<'a>>::Output,
566 {
567 DataPayload(DataPayloadInner::Yoke(match &self.0 {
568 DataPayloadInner::Yoke(yoke) => yoke.map_project_cloned(f),
569 DataPayloadInner::StaticRef(r) => {
570 let output: <M2::Yokeable as Yokeable<'static>>::Output =
571 f(Yokeable::transform(*r), PhantomData);
572 // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable;
573 // we're going from 'static to 'static, however in a generic context it's not
574 // clear to the compiler that that is the case. We have to use the unsafe make API to do this.
575 let yokeable: M2::Yokeable = unsafe { M2::Yokeable::make(output) };
576 Yoke::new_owned(yokeable).convert_cart_into_option_pointer()
577 }
578 }))
579 }
580
581 /// Version of [`DataPayload::map_project()`] that bubbles up an error from `f`.
582 ///
583 /// # Examples
584 ///
585 /// Same example as above, but bubble up an error:
586 ///
587 /// ```
588 /// // Same imports and definitions as above
589 /// # use icu_provider::hello_world::*;
590 /// # use icu_provider::prelude::*;
591 /// # use std::borrow::Cow;
592 /// # struct HelloWorldV1MessageMarker;
593 /// # impl DataMarker for HelloWorldV1MessageMarker {
594 /// # type Yokeable = Cow<'static, str>;
595 /// # }
596 ///
597 /// let p1: DataPayload<HelloWorldV1Marker> =
598 /// DataPayload::from_owned(HelloWorldV1 {
599 /// message: Cow::Borrowed("Hello World"),
600 /// });
601 ///
602 /// assert_eq!("Hello World", p1.get().message);
603 ///
604 /// let string_to_append = "Extra";
605 /// let p2: DataPayload<HelloWorldV1MessageMarker> =
606 /// p1.try_map_project(|mut obj, _| {
607 /// if obj.message.is_empty() {
608 /// return Err("Example error");
609 /// }
610 /// obj.message.to_mut().push_str(string_to_append);
611 /// Ok(obj.message)
612 /// })?;
613 ///
614 /// assert_eq!("Hello WorldExtra", p2.get());
615 /// # Ok::<(), &'static str>(())
616 /// ```
617 #[allow(clippy::type_complexity)]
618 pub fn try_map_project<M2, F, E>(self, f: F) -> Result<DataPayload<M2>, E>
619 where
620 M2: DataMarker,
621 F: for<'a> FnOnce(
622 <M::Yokeable as Yokeable<'a>>::Output,
623 PhantomData<&'a ()>,
624 ) -> Result<<M2::Yokeable as Yokeable<'a>>::Output, E>,
625 M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>,
626 {
627 Ok(DataPayload(DataPayloadInner::Yoke(
628 match self.0 {
629 DataPayloadInner::Yoke(yoke) => yoke,
630 DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))
631 .convert_cart_into_option_pointer(),
632 }
633 .try_map_project(f)?,
634 )))
635 }
636
637 /// Version of [`DataPayload::map_project_cloned()`] that bubbles up an error from `f`.
638 ///
639 /// # Examples
640 ///
641 /// Same example as above, but bubble up an error:
642 ///
643 /// ```
644 /// // Same imports and definitions as above
645 /// # use icu_provider::hello_world::*;
646 /// # use icu_provider::prelude::*;
647 /// # use std::borrow::Cow;
648 /// # struct HelloWorldV1MessageMarker;
649 /// # impl DataMarker for HelloWorldV1MessageMarker {
650 /// # type Yokeable = Cow<'static, str>;
651 /// # }
652 ///
653 /// let p1: DataPayload<HelloWorldV1Marker> =
654 /// DataPayload::from_owned(HelloWorldV1 {
655 /// message: Cow::Borrowed("Hello World"),
656 /// });
657 ///
658 /// assert_eq!("Hello World", p1.get().message);
659 ///
660 /// let string_to_append = "Extra";
661 /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1
662 /// .try_map_project_cloned(|obj, _| {
663 /// if obj.message.is_empty() {
664 /// return Err("Example error");
665 /// }
666 /// let mut message = obj.message.clone();
667 /// message.to_mut().push_str(string_to_append);
668 /// Ok(message)
669 /// })?;
670 ///
671 /// // Note: p1 is still valid, but the values no longer equal.
672 /// assert_ne!(p1.get().message, *p2.get());
673 /// assert_eq!("Hello WorldExtra", p2.get());
674 /// # Ok::<(), &'static str>(())
675 /// ```
676 #[allow(clippy::type_complexity)]
677 pub fn try_map_project_cloned<'this, M2, F, E>(&'this self, f: F) -> Result<DataPayload<M2>, E>
678 where
679 M2: DataMarker,
680 F: for<'a> FnOnce(
681 &'this <M::Yokeable as Yokeable<'a>>::Output,
682 PhantomData<&'a ()>,
683 ) -> Result<<M2::Yokeable as Yokeable<'a>>::Output, E>,
684 {
685 Ok(DataPayload(DataPayloadInner::Yoke(match &self.0 {
686 DataPayloadInner::Yoke(yoke) => yoke.try_map_project_cloned(f)?,
687 DataPayloadInner::StaticRef(r) => {
688 let output: <M2::Yokeable as Yokeable<'static>>::Output =
689 f(Yokeable::transform(*r), PhantomData)?;
690 // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable
691 Yoke::new_owned(unsafe { M2::Yokeable::make(output) })
692 .convert_cart_into_option_pointer()
693 }
694 })))
695 }
696
697 /// Convert between two [`DataMarker`] types that are compatible with each other
698 /// with compile-time type checking.
699 ///
700 /// This happens if they both have the same [`DataMarker::Yokeable`] type.
701 ///
702 /// Can be used to erase the key of a data payload in cases where multiple keys correspond
703 /// to the same data struct.
704 ///
705 /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`].
706 ///
707 /// # Examples
708 ///
709 /// ```no_run
710 /// use icu_provider::hello_world::*;
711 /// use icu_provider::prelude::*;
712 ///
713 /// struct CustomHelloWorldV1Marker;
714 /// impl DataMarker for CustomHelloWorldV1Marker {
715 /// type Yokeable = HelloWorldV1<'static>;
716 /// }
717 ///
718 /// let hello_world: DataPayload<HelloWorldV1Marker> = todo!();
719 /// let custom: DataPayload<CustomHelloWorldV1Marker> = hello_world.cast();
720 /// ```
721 #[inline]
722 pub fn cast<M2>(self) -> DataPayload<M2>
723 where
724 M2: DataMarker<Yokeable = M::Yokeable>,
725 {
726 DataPayload(match self.0 {
727 DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke),
728 DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(r),
729 })
730 }
731
732 /// Convert a mutable reference of a [`DataPayload`] to another mutable reference
733 /// of the same type with runtime type checking.
734 ///
735 /// Primarily useful to convert from a generic to a concrete marker type.
736 ///
737 /// If the `M2` type argument does not match the true marker type, a `DataError` is returned.
738 ///
739 /// For compile-time static casting, use [`DataPayload::cast()`].
740 ///
741 /// # Examples
742 ///
743 /// Change the results of a particular request based on key:
744 ///
745 /// ```
746 /// use icu_locid::locale;
747 /// use icu_provider::hello_world::*;
748 /// use icu_provider::prelude::*;
749 ///
750 /// struct MyWrapper<P> {
751 /// inner: P,
752 /// }
753 ///
754 /// impl<M, P> DataProvider<M> for MyWrapper<P>
755 /// where
756 /// M: KeyedDataMarker,
757 /// P: DataProvider<M>,
758 /// {
759 /// #[inline]
760 /// fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
761 /// let mut res = self.inner.load(req)?;
762 /// if let Some(ref mut generic_payload) = res.payload {
763 /// let mut cast_result =
764 /// generic_payload.dynamic_cast_mut::<HelloWorldV1Marker>();
765 /// if let Ok(ref mut concrete_payload) = cast_result {
766 /// // Add an emoji to the hello world message
767 /// concrete_payload.with_mut(|data| {
768 /// data.message.to_mut().insert_str(0, "✨ ");
769 /// });
770 /// }
771 /// }
772 /// Ok(res)
773 /// }
774 /// }
775 ///
776 /// let provider = MyWrapper {
777 /// inner: HelloWorldProvider,
778 /// };
779 /// let formatter =
780 /// HelloWorldFormatter::try_new_unstable(&provider, &locale!("de").into())
781 /// .unwrap();
782 ///
783 /// assert_eq!(formatter.format_to_string(), "✨ Hallo Welt");
784 /// ```
785 #[inline]
786 pub fn dynamic_cast_mut<M2>(&mut self) -> Result<&mut DataPayload<M2>, DataError>
787 where
788 M2: DataMarker,
789 {
790 let this: &mut dyn core::any::Any = self;
791 if let Some(this) = this.downcast_mut() {
792 Ok(this)
793 } else {
794 Err(DataError::for_type::<M2>().with_str_context(core::any::type_name::<M>()))
795 }
796 }
797}
798
799impl DataPayload<BufferMarker> {
800 /// Converts an owned byte buffer into a `DataPayload<BufferMarker>`.
801 pub fn from_owned_buffer(buffer: Box<[u8]>) -> Self {
802 let yoke = Yoke::attach_to_cart(SelectedRc::new(buffer), |b| &**b)
803 .wrap_cart_in_option()
804 .convert_cart_into_option_pointer();
805 Self(DataPayloadInner::Yoke(yoke))
806 }
807
808 /// Converts a yoked byte buffer into a `DataPayload<BufferMarker>`.
809 pub fn from_yoked_buffer(yoke: Yoke<&'static [u8], Option<Cart>>) -> Self {
810 let yoke = Cart::unwrap_cart(yoke);
811 Self(DataPayloadInner::Yoke(
812 yoke.convert_cart_into_option_pointer(),
813 ))
814 }
815
816 /// Converts a static byte buffer into a `DataPayload<BufferMarker>`.
817 pub fn from_static_buffer(buffer: &'static [u8]) -> Self {
818 Self(DataPayloadInner::Yoke(
819 Yoke::new_owned(buffer).convert_cart_into_option_pointer(),
820 ))
821 }
822}
823
824impl<M> Default for DataPayload<M>
825where
826 M: DataMarker,
827 M::Yokeable: Default,
828{
829 fn default() -> Self {
830 Self::from_owned(Default::default())
831 }
832}
833
834impl<M, O> DataPayloadOr<M, O>
835where
836 M: DataMarker,
837{
838 /// Creates a [`DataPayloadOr`] from a [`DataPayload`].
839 #[inline]
840 pub fn from_payload(payload: DataPayload<M>) -> Self {
841 match payload.0 {
842 DataPayloadInner::Yoke(yoke) => Self(DataPayloadOrInner::Yoke(yoke)),
843 DataPayloadInner::StaticRef(r) => Self(DataPayloadOrInner::Inner(
844 DataPayloadOrInnerInner::StaticRef(r),
845 )),
846 }
847 }
848
849 /// Creates a [`DataPayloadOr`] from the other type `O`.
850 #[inline]
851 pub fn from_other(other: O) -> Self {
852 Self(DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(
853 other,
854 )))
855 }
856
857 /// Gets the value from this [`DataPayload`] as `Ok` or the other type as `Err`.
858 #[allow(clippy::needless_lifetimes)]
859 #[inline]
860 pub fn get<'a>(&'a self) -> Result<&'a <M::Yokeable as Yokeable<'a>>::Output, &'a O> {
861 match &self.0 {
862 DataPayloadOrInner::Yoke(yoke) => Ok(yoke.get()),
863 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(r)) => {
864 Ok(Yokeable::transform(*r))
865 }
866 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o)) => Err(o),
867 }
868 }
869
870 /// Consumes this [`DataPayloadOr`], returning either the wrapped
871 /// [`DataPayload`] or the other type.
872 #[inline]
873 pub fn into_inner(self) -> Result<DataPayload<M>, O> {
874 match self.0 {
875 DataPayloadOrInner::Yoke(yoke) => Ok(DataPayload(DataPayloadInner::Yoke(yoke))),
876 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::StaticRef(r)) => {
877 Ok(DataPayload(DataPayloadInner::StaticRef(r)))
878 }
879 DataPayloadOrInner::Inner(DataPayloadOrInnerInner::Other(o)) => Err(o),
880 }
881 }
882}
883
884/// A response object containing an object as payload and metadata about it.
885#[allow(clippy::exhaustive_structs)] // this type is stable
886pub struct DataResponse<M>
887where
888 M: DataMarker,
889{
890 /// Metadata about the returned object.
891 pub metadata: DataResponseMetadata,
892
893 /// The object itself; `None` if it was not loaded.
894 pub payload: Option<DataPayload<M>>,
895}
896
897impl<M> DataResponse<M>
898where
899 M: DataMarker,
900{
901 /// Takes ownership of the underlying payload. Error if not present.
902 ///
903 /// To take the metadata, too, use [`Self::take_metadata_and_payload()`].
904 #[inline]
905 pub fn take_payload(self) -> Result<DataPayload<M>, DataError> {
906 Ok(self.take_metadata_and_payload()?.1)
907 }
908
909 /// Takes ownership of the underlying metadata and payload. Error if payload is not present.
910 #[inline]
911 pub fn take_metadata_and_payload(
912 self,
913 ) -> Result<(DataResponseMetadata, DataPayload<M>), DataError> {
914 Ok((
915 self.metadata,
916 self.payload
917 .ok_or_else(|| DataErrorKind::MissingPayload.with_type_context::<M>())?,
918 ))
919 }
920
921 /// Convert between two [`DataMarker`] types that are compatible with each other
922 /// with compile-time type checking.
923 ///
924 /// This happens if they both have the same [`DataMarker::Yokeable`] type.
925 ///
926 /// Can be used to erase the key of a data payload in cases where multiple keys correspond
927 /// to the same data struct.
928 ///
929 /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`].
930 #[inline]
931 pub fn cast<M2>(self) -> DataResponse<M2>
932 where
933 M2: DataMarker<Yokeable = M::Yokeable>,
934 {
935 match self.payload {
936 Some(payload) => DataResponse {
937 metadata: self.metadata,
938 payload: Some(payload.cast()),
939 },
940 None => DataResponse {
941 metadata: self.metadata,
942 payload: None,
943 },
944 }
945 }
946}
947
948impl<M> TryFrom<DataResponse<M>> for DataPayload<M>
949where
950 M: DataMarker,
951{
952 type Error = DataError;
953
954 fn try_from(response: DataResponse<M>) -> Result<Self, Self::Error> {
955 response.take_payload()
956 }
957}
958
959impl<M> Debug for DataResponse<M>
960where
961 M: DataMarker,
962 for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug,
963{
964 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
965 write!(
966 f,
967 "DataResponse {{ metadata: {:?}, payload: {:?} }}",
968 self.metadata, self.payload
969 )
970 }
971}
972
973/// Cloning a DataResponse is generally a cheap operation.
974/// See notes in the `Clone` impl for [`Yoke`].
975///
976/// # Examples
977///
978/// ```no_run
979/// use icu_provider::hello_world::*;
980/// use icu_provider::prelude::*;
981///
982/// let resp1: DataResponse<HelloWorldV1Marker> = todo!();
983/// let resp2 = resp1.clone();
984/// ```
985impl<M> Clone for DataResponse<M>
986where
987 M: DataMarker,
988 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
989{
990 fn clone(&self) -> Self {
991 Self {
992 metadata: self.metadata.clone(),
993 payload: self.payload.clone(),
994 }
995 }
996}
997
998#[test]
999fn test_debug() {
1000 use crate::hello_world::*;
1001 use alloc::borrow::Cow;
1002 let resp = DataResponse::<HelloWorldV1Marker> {
1003 metadata: Default::default(),
1004 payload: Some(DataPayload::from_owned(HelloWorldV1 {
1005 message: Cow::Borrowed("foo"),
1006 })),
1007 };
1008 assert_eq!("DataResponse { metadata: DataResponseMetadata { locale: None, buffer_format: None }, payload: Some(HelloWorldV1 { message: \"foo\" }) }", format!("{resp:?}"));
1009}