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