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}