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