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}