1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::buf::BufferMarker;
6use crate::error::{DataError, DataErrorKind};
7use crate::marker::DataMarker;
8use crate::request::DataLocale;
9use alloc::boxed::Box;
10use core::convert::TryFrom;
11use core::fmt::Debug;
12use core::marker::PhantomData;
13use core::ops::Deref;
14use yoke::trait_hack::YokeTraitHack;
15use yoke::*;
16
17#[cfg(not(feature = "sync"))]
18use alloc::rc::Rc as SelectedRc;
19#[cfg(feature = "sync")]
20use alloc::sync::Arc as SelectedRc;
21
22/// A response object containing metadata about the returned data.
23#[derive(Debug, Clone, PartialEq, Default)]
24#[non_exhaustive]
25pub struct DataResponseMetadata {
26 /// The resolved locale of the returned data, if locale fallbacking was performed.
27 pub locale: Option<DataLocale>,
28 /// The format of the buffer for buffer-backed data, if known (for example, JSON).
29 pub buffer_format: Option<crate::buf::BufferFormat>,
30}
31
32/// A container for data payloads returned from a data provider.
33///
34/// [`DataPayload`] is built on top of the [`yoke`] framework, which allows for cheap, zero-copy
35/// operations on data via the use of self-references.
36///
37/// The type of the data stored in [`DataPayload`] is determined by the [`DataMarker`] type parameter.
38///
39/// ## Accessing the data
40///
41/// To get a reference to the data inside [`DataPayload`], use [`DataPayload::get()`]. If you need
42/// to store the data for later use, you need to store the [`DataPayload`] itself, since `get` only
43/// returns a reference with an ephemeral lifetime.
44///
45/// ## Mutating the data
46///
47/// To modify the data stored in a [`DataPayload`], use [`DataPayload::with_mut()`].
48///
49/// ## Transforming the data to a different type
50///
51/// To transform a [`DataPayload`] to a different type backed by the same data store (cart), use
52/// [`DataPayload::map_project()`] or one of its sister methods.
53///
54/// # Cargo feature: `sync`
55///
56/// By default, the payload uses non-concurrent reference counting internally, and hence is neither
57/// [`Sync`] nor [`Send`]; if these traits are required, the `sync` Cargo feature can be enabled.
58///
59/// # Examples
60///
61/// Basic usage, using the `HelloWorldV1Marker` marker:
62///
63/// ```
64/// use icu_provider::hello_world::*;
65/// use icu_provider::prelude::*;
66/// use std::borrow::Cow;
67///
68/// let payload = DataPayload::<HelloWorldV1Marker>::from_owned(HelloWorldV1 {
69/// message: Cow::Borrowed("Demo"),
70/// });
71///
72/// assert_eq!("Demo", payload.get().message);
73/// ```
74pub struct DataPayload<M: DataMarker>(pub(crate) DataPayloadInner<M>);
75
76pub(crate) enum DataPayloadInner<M: DataMarker> {
77 Yoke(Yoke<M::Yokeable, Option<Cart>>),
78 StaticRef(&'static M::Yokeable),
79}
80
81/// The type of the "cart" that is used by `DataPayload`.
82#[derive(Clone, Debug)]
83#[allow(clippy::redundant_allocation)] // false positive, it's cheaper to wrap an existing Box in an Rc than to reallocate a huge Rc
84pub struct Cart(SelectedRc<Box<[u8]>>);
85
86impl Deref for Cart {
87 type Target = Box<[u8]>;
88 fn deref(&self) -> &Self::Target {
89 &self.0
90 }
91}
92// Safe because both Rc and Arc are StableDeref, and our impl delegates.
93unsafe impl stable_deref_trait::StableDeref for Cart {}
94// Safe because both Rc and Arc are CloneableCart, and our impl delegates.
95unsafe impl yoke::CloneableCart for Cart {}
96
97impl Cart {
98 /// Creates a `Yoke<Y, Option<Cart>>` from owned bytes by applying `f`.
99 pub fn try_make_yoke<Y, F, E>(cart: Box<[u8]>, f: F) -> Result<Yoke<Y, Option<Self>>, E>
100 where
101 for<'a> Y: Yokeable<'a>,
102 F: FnOnce(&[u8]) -> Result<<Y as Yokeable>::Output, E>,
103 {
104 Yoke::try_attach_to_cart(SelectedRc::new(cart), |b| f(b))
105 // Safe because the cart is only wrapped
106 .map(|yoke| unsafe { yoke.replace_cart(Cart) })
107 .map(op:Yoke::wrap_cart_in_option)
108 }
109}
110
111impl<M> Debug for DataPayload<M>
112where
113 M: DataMarker,
114 for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug,
115{
116 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
117 self.get().fmt(f)
118 }
119}
120
121/// Cloning a DataPayload is generally a cheap operation.
122/// See notes in the `Clone` impl for [`Yoke`].
123///
124/// # Examples
125///
126/// ```no_run
127/// use icu_provider::hello_world::*;
128/// use icu_provider::prelude::*;
129///
130/// let resp1: DataPayload<HelloWorldV1Marker> = todo!();
131/// let resp2 = resp1.clone();
132/// ```
133impl<M> Clone for DataPayload<M>
134where
135 M: DataMarker,
136 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
137{
138 fn clone(&self) -> Self {
139 Self(match &self.0 {
140 DataPayloadInner::Yoke(yoke: &Yoke<::Yokeable, …>) => DataPayloadInner::Yoke(yoke.clone()),
141 DataPayloadInner::StaticRef(r: &&::Yokeable) => DataPayloadInner::StaticRef(*r),
142 })
143 }
144}
145
146impl<M> PartialEq for DataPayload<M>
147where
148 M: DataMarker,
149 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: PartialEq,
150{
151 fn eq(&self, other: &Self) -> bool {
152 YokeTraitHack(self.get()).into_ref() == YokeTraitHack(other.get()).into_ref()
153 }
154}
155
156impl<M> Eq for DataPayload<M>
157where
158 M: DataMarker,
159 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Eq,
160{
161}
162
163#[test]
164fn test_clone_eq() {
165 use crate::hello_world::*;
166 let p1: DataPayload = DataPayload::<HelloWorldV1Marker>::from_static_str("Demo");
167 #[allow(clippy::redundant_clone)]
168 let p2 = p1.clone();
169 assert_eq!(p1, p2);
170}
171
172impl<M> DataPayload<M>
173where
174 M: DataMarker,
175{
176 /// Convert a fully owned (`'static`) data struct into a DataPayload.
177 ///
178 /// This constructor creates `'static` payloads.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use icu_provider::hello_world::*;
184 /// use icu_provider::prelude::*;
185 /// use std::borrow::Cow;
186 ///
187 /// let local_struct = HelloWorldV1 {
188 /// message: Cow::Owned("example".to_owned()),
189 /// };
190 ///
191 /// let payload =
192 /// DataPayload::<HelloWorldV1Marker>::from_owned(local_struct.clone());
193 ///
194 /// assert_eq!(payload.get(), &local_struct);
195 /// ```
196 #[inline]
197 pub const fn from_owned(data: M::Yokeable) -> Self {
198 Self(DataPayloadInner::Yoke(Yoke::new_owned(data)))
199 }
200
201 #[doc(hidden)]
202 #[inline]
203 pub const fn from_static_ref(data: &'static M::Yokeable) -> Self {
204 Self(DataPayloadInner::StaticRef(data))
205 }
206
207 /// Convert a DataPayload that was created via [`DataPayload::from_owned()`] back into the
208 /// concrete type used to construct it.
209 pub fn try_unwrap_owned(self) -> Result<M::Yokeable, DataError> {
210 match self.0 {
211 DataPayloadInner::Yoke(yoke) => yoke.try_into_yokeable().ok(),
212 DataPayloadInner::StaticRef(_) => None,
213 }
214 .ok_or(DataErrorKind::InvalidState.with_str_context("try_unwrap_owned"))
215 }
216
217 /// Mutate the data contained in this DataPayload.
218 ///
219 /// For safety, all mutation operations must take place within a helper function that cannot
220 /// borrow data from the surrounding context.
221 ///
222 /// # Examples
223 ///
224 /// Basic usage:
225 ///
226 /// ```
227 /// use icu_provider::hello_world::HelloWorldV1Marker;
228 /// use icu_provider::prelude::*;
229 ///
230 /// let mut payload =
231 /// DataPayload::<HelloWorldV1Marker>::from_static_str("Hello");
232 ///
233 /// payload.with_mut(|s| s.message.to_mut().push_str(" World"));
234 ///
235 /// assert_eq!("Hello World", payload.get().message);
236 /// ```
237 ///
238 /// To transfer data from the context into the data struct, use the `move` keyword:
239 ///
240 /// ```
241 /// use icu_provider::hello_world::HelloWorldV1Marker;
242 /// use icu_provider::prelude::*;
243 ///
244 /// let mut payload =
245 /// DataPayload::<HelloWorldV1Marker>::from_static_str("Hello");
246 ///
247 /// let suffix = " World";
248 /// payload.with_mut(move |s| s.message.to_mut().push_str(suffix));
249 ///
250 /// assert_eq!("Hello World", payload.get().message);
251 /// ```
252 pub fn with_mut<'a, F>(&'a mut self, f: F)
253 where
254 F: 'static + for<'b> FnOnce(&'b mut <M::Yokeable as Yokeable<'a>>::Output),
255 M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>,
256 {
257 if let DataPayloadInner::StaticRef(r) = self.0 {
258 self.0 = DataPayloadInner::Yoke(Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)));
259 }
260 match &mut self.0 {
261 DataPayloadInner::Yoke(yoke) => yoke.with_mut(f),
262 _ => unreachable!(),
263 }
264 }
265
266 /// Borrows the underlying data.
267 ///
268 /// This function should be used like `Deref` would normally be used. For more information on
269 /// why DataPayload cannot implement `Deref`, see the `yoke` crate.
270 ///
271 /// # Examples
272 ///
273 /// ```
274 /// use icu_provider::hello_world::HelloWorldV1Marker;
275 /// use icu_provider::prelude::*;
276 ///
277 /// let payload = DataPayload::<HelloWorldV1Marker>::from_static_str("Demo");
278 ///
279 /// assert_eq!("Demo", payload.get().message);
280 /// ```
281 #[inline]
282 #[allow(clippy::needless_lifetimes)]
283 pub fn get<'a>(&'a self) -> &'a <M::Yokeable as Yokeable<'a>>::Output {
284 match &self.0 {
285 DataPayloadInner::Yoke(yoke) => yoke.get(),
286 DataPayloadInner::StaticRef(r) => Yokeable::transform(*r),
287 }
288 }
289
290 /// Maps `DataPayload<M>` to `DataPayload<M2>` by projecting it with [`Yoke::map_project`].
291 ///
292 /// This is accomplished by a function that takes `M`'s data type and returns `M2`'s data
293 /// type. The function takes a second argument which should be ignored. For more details,
294 /// see [`Yoke::map_project()`].
295 ///
296 /// The standard [`DataPayload::map_project()`] function moves `self` and cannot capture any
297 /// data from its context. Use one of the sister methods if you need these capabilities:
298 ///
299 /// - [`DataPayload::map_project_cloned()`] if you don't have ownership of `self`
300 /// - [`DataPayload::try_map_project()`] to bubble up an error
301 /// - [`DataPayload::try_map_project_cloned()`] to do both of the above
302 ///
303 /// # Examples
304 ///
305 /// Map from `HelloWorldV1` to a `Cow<str>` containing just the message:
306 ///
307 /// ```
308 /// use icu_provider::hello_world::*;
309 /// use icu_provider::prelude::*;
310 /// use std::borrow::Cow;
311 ///
312 /// // A custom marker type is required when using `map_project`. The Yokeable should be the
313 /// // target type, and the Cart should correspond to the type being transformed.
314 ///
315 /// struct HelloWorldV1MessageMarker;
316 /// impl DataMarker for HelloWorldV1MessageMarker {
317 /// type Yokeable = Cow<'static, str>;
318 /// }
319 ///
320 /// let p1: DataPayload<HelloWorldV1Marker> = DataPayload::from_owned(HelloWorldV1 {
321 /// message: Cow::Borrowed("Hello World"),
322 /// });
323 ///
324 /// assert_eq!("Hello World", p1.get().message);
325 ///
326 /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1.map_project(|obj, _| obj.message);
327 ///
328 /// // Note: at this point, p1 has been moved.
329 /// assert_eq!("Hello World", p2.get());
330 /// ```
331 #[allow(clippy::type_complexity)]
332 pub fn map_project<M2, F>(self, f: F) -> DataPayload<M2>
333 where
334 M2: DataMarker,
335 F: for<'a> FnOnce(
336 <M::Yokeable as Yokeable<'a>>::Output,
337 PhantomData<&'a ()>,
338 ) -> <M2::Yokeable as Yokeable<'a>>::Output,
339 M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>,
340 {
341 DataPayload(DataPayloadInner::Yoke(
342 match self.0 {
343 DataPayloadInner::Yoke(yoke) => yoke,
344 DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)),
345 }
346 .map_project(f),
347 ))
348 }
349
350 /// Version of [`DataPayload::map_project()`] that borrows `self` instead of moving `self`.
351 ///
352 /// # Examples
353 ///
354 /// Same example as above, but this time, do not move out of `p1`:
355 ///
356 /// ```
357 /// // Same imports and definitions as above
358 /// # use icu_provider::hello_world::*;
359 /// # use icu_provider::prelude::*;
360 /// # use std::borrow::Cow;
361 /// # struct HelloWorldV1MessageMarker;
362 /// # impl DataMarker for HelloWorldV1MessageMarker {
363 /// # type Yokeable = Cow<'static, str>;
364 /// # }
365 ///
366 /// let p1: DataPayload<HelloWorldV1Marker> =
367 /// DataPayload::from_owned(HelloWorldV1 {
368 /// message: Cow::Borrowed("Hello World"),
369 /// });
370 ///
371 /// assert_eq!("Hello World", p1.get().message);
372 ///
373 /// let p2: DataPayload<HelloWorldV1MessageMarker> =
374 /// p1.map_project_cloned(|obj, _| obj.message.clone());
375 ///
376 /// // Note: p1 is still valid.
377 /// assert_eq!(p1.get().message, *p2.get());
378 /// ```
379 #[allow(clippy::type_complexity)]
380 pub fn map_project_cloned<'this, M2, F>(&'this self, f: F) -> DataPayload<M2>
381 where
382 M2: DataMarker,
383 F: for<'a> FnOnce(
384 &'this <M::Yokeable as Yokeable<'a>>::Output,
385 PhantomData<&'a ()>,
386 ) -> <M2::Yokeable as Yokeable<'a>>::Output,
387 {
388 DataPayload(DataPayloadInner::Yoke(match &self.0 {
389 DataPayloadInner::Yoke(yoke) => yoke.map_project_cloned(f),
390 DataPayloadInner::StaticRef(r) => {
391 let output: <M2::Yokeable as Yokeable<'static>>::Output =
392 f(Yokeable::transform(*r), PhantomData);
393 // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable
394 let yokeable: M2::Yokeable = unsafe { M2::Yokeable::make(output) };
395 Yoke::new_owned(yokeable)
396 }
397 }))
398 }
399
400 /// Version of [`DataPayload::map_project()`] that bubbles up an error from `f`.
401 ///
402 /// # Examples
403 ///
404 /// Same example as above, but bubble up an error:
405 ///
406 /// ```
407 /// // Same imports and definitions as above
408 /// # use icu_provider::hello_world::*;
409 /// # use icu_provider::prelude::*;
410 /// # use std::borrow::Cow;
411 /// # struct HelloWorldV1MessageMarker;
412 /// # impl DataMarker for HelloWorldV1MessageMarker {
413 /// # type Yokeable = Cow<'static, str>;
414 /// # }
415 ///
416 /// let p1: DataPayload<HelloWorldV1Marker> =
417 /// DataPayload::from_owned(HelloWorldV1 {
418 /// message: Cow::Borrowed("Hello World"),
419 /// });
420 ///
421 /// assert_eq!("Hello World", p1.get().message);
422 ///
423 /// let string_to_append = "Extra";
424 /// let p2: DataPayload<HelloWorldV1MessageMarker> =
425 /// p1.try_map_project(|mut obj, _| {
426 /// if obj.message.is_empty() {
427 /// return Err("Example error");
428 /// }
429 /// obj.message.to_mut().push_str(string_to_append);
430 /// Ok(obj.message)
431 /// })?;
432 ///
433 /// assert_eq!("Hello WorldExtra", p2.get());
434 /// # Ok::<(), &'static str>(())
435 /// ```
436 #[allow(clippy::type_complexity)]
437 pub fn try_map_project<M2, F, E>(self, f: F) -> Result<DataPayload<M2>, E>
438 where
439 M2: DataMarker,
440 F: for<'a> FnOnce(
441 <M::Yokeable as Yokeable<'a>>::Output,
442 PhantomData<&'a ()>,
443 ) -> Result<<M2::Yokeable as Yokeable<'a>>::Output, E>,
444 M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>,
445 {
446 Ok(DataPayload(DataPayloadInner::Yoke(
447 match self.0 {
448 DataPayloadInner::Yoke(yoke) => yoke,
449 DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)),
450 }
451 .try_map_project(f)?,
452 )))
453 }
454
455 /// Version of [`DataPayload::map_project_cloned()`] that bubbles up an error from `f`.
456 ///
457 /// # Examples
458 ///
459 /// Same example as above, but bubble up an error:
460 ///
461 /// ```
462 /// // Same imports and definitions as above
463 /// # use icu_provider::hello_world::*;
464 /// # use icu_provider::prelude::*;
465 /// # use std::borrow::Cow;
466 /// # struct HelloWorldV1MessageMarker;
467 /// # impl DataMarker for HelloWorldV1MessageMarker {
468 /// # type Yokeable = Cow<'static, str>;
469 /// # }
470 ///
471 /// let p1: DataPayload<HelloWorldV1Marker> =
472 /// DataPayload::from_owned(HelloWorldV1 {
473 /// message: Cow::Borrowed("Hello World"),
474 /// });
475 ///
476 /// assert_eq!("Hello World", p1.get().message);
477 ///
478 /// let string_to_append = "Extra";
479 /// let p2: DataPayload<HelloWorldV1MessageMarker> = p1
480 /// .try_map_project_cloned(|obj, _| {
481 /// if obj.message.is_empty() {
482 /// return Err("Example error");
483 /// }
484 /// let mut message = obj.message.clone();
485 /// message.to_mut().push_str(string_to_append);
486 /// Ok(message)
487 /// })?;
488 ///
489 /// // Note: p1 is still valid, but the values no longer equal.
490 /// assert_ne!(p1.get().message, *p2.get());
491 /// assert_eq!("Hello WorldExtra", p2.get());
492 /// # Ok::<(), &'static str>(())
493 /// ```
494 #[allow(clippy::type_complexity)]
495 pub fn try_map_project_cloned<'this, M2, F, E>(&'this self, f: F) -> Result<DataPayload<M2>, E>
496 where
497 M2: DataMarker,
498 F: for<'a> FnOnce(
499 &'this <M::Yokeable as Yokeable<'a>>::Output,
500 PhantomData<&'a ()>,
501 ) -> Result<<M2::Yokeable as Yokeable<'a>>::Output, E>,
502 {
503 Ok(DataPayload(DataPayloadInner::Yoke(match &self.0 {
504 DataPayloadInner::Yoke(yoke) => yoke.try_map_project_cloned(f)?,
505 DataPayloadInner::StaticRef(r) => {
506 let output: <M2::Yokeable as Yokeable<'static>>::Output =
507 f(Yokeable::transform(*r), PhantomData)?;
508 // Safety: <M2::Yokeable as Yokeable<'static>>::Output is the same type as M2::Yokeable
509 Yoke::new_owned(unsafe { M2::Yokeable::make(output) })
510 }
511 })))
512 }
513
514 /// Convert between two [`DataMarker`] types that are compatible with each other
515 /// with compile-time type checking.
516 ///
517 /// This happens if they both have the same [`DataMarker::Yokeable`] type.
518 ///
519 /// Can be used to erase the key of a data payload in cases where multiple keys correspond
520 /// to the same data struct.
521 ///
522 /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`].
523 ///
524 /// # Examples
525 ///
526 /// ```no_run
527 /// use icu_locid::locale;
528 /// use icu_provider::hello_world::*;
529 /// use icu_provider::prelude::*;
530 ///
531 /// struct CustomHelloWorldV1Marker;
532 /// impl DataMarker for CustomHelloWorldV1Marker {
533 /// type Yokeable = HelloWorldV1<'static>;
534 /// }
535 ///
536 /// let hello_world: DataPayload<HelloWorldV1Marker> = todo!();
537 /// let custom: DataPayload<CustomHelloWorldV1Marker> = hello_world.cast();
538 /// ```
539 #[inline]
540 pub fn cast<M2>(self) -> DataPayload<M2>
541 where
542 M2: DataMarker<Yokeable = M::Yokeable>,
543 {
544 DataPayload(match self.0 {
545 DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke),
546 DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(r),
547 })
548 }
549
550 /// Convert a mutable reference of a [`DataPayload`] to another mutable reference
551 /// of the same type with runtime type checking.
552 ///
553 /// Primarily useful to convert from a generic to a concrete marker type.
554 ///
555 /// If the `M2` type argument does not match the true marker type, a `DataError` is returned.
556 ///
557 /// For compile-time static casting, use [`DataPayload::cast()`].
558 ///
559 /// # Examples
560 ///
561 /// Change the results of a particular request based on key:
562 ///
563 /// ```
564 /// use icu_locid::locale;
565 /// use icu_provider::hello_world::*;
566 /// use icu_provider::prelude::*;
567 ///
568 /// struct MyWrapper<P> {
569 /// inner: P,
570 /// }
571 ///
572 /// impl<M, P> DataProvider<M> for MyWrapper<P>
573 /// where
574 /// M: KeyedDataMarker,
575 /// P: DataProvider<M>,
576 /// {
577 /// #[inline]
578 /// fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
579 /// let mut res = self.inner.load(req)?;
580 /// if let Some(ref mut generic_payload) = res.payload {
581 /// let mut cast_result =
582 /// generic_payload.dynamic_cast_mut::<HelloWorldV1Marker>();
583 /// if let Ok(ref mut concrete_payload) = cast_result {
584 /// // Add an emoji to the hello world message
585 /// concrete_payload.with_mut(|data| {
586 /// data.message.to_mut().insert_str(0, "✨ ");
587 /// });
588 /// }
589 /// }
590 /// Ok(res)
591 /// }
592 /// }
593 ///
594 /// let provider = MyWrapper {
595 /// inner: HelloWorldProvider,
596 /// };
597 /// let formatter =
598 /// HelloWorldFormatter::try_new_unstable(&provider, &locale!("de").into())
599 /// .unwrap();
600 ///
601 /// assert_eq!(formatter.format_to_string(), "✨ Hallo Welt");
602 /// ```
603 #[inline]
604 pub fn dynamic_cast_mut<M2>(&mut self) -> Result<&mut DataPayload<M2>, DataError>
605 where
606 M2: DataMarker,
607 {
608 let this: &mut dyn core::any::Any = self;
609 if let Some(this) = this.downcast_mut() {
610 Ok(this)
611 } else {
612 Err(DataError::for_type::<M2>().with_str_context(core::any::type_name::<M>()))
613 }
614 }
615}
616
617impl DataPayload<BufferMarker> {
618 /// Converts an owned byte buffer into a `DataPayload<BufferMarker>`.
619 pub fn from_owned_buffer(buffer: Box<[u8]>) -> Self {
620 let yoke: Yoke<&[u8], Rc>> = Yoke::attach_to_cart(cart:SelectedRc::new(buffer), |b: &Box<[u8]>| &**b);
621 // Safe because cart is wrapped
622 let yoke: Yoke<&[u8], Option> = unsafe { yoke.replace_cart(|b: Rc>| Some(Cart(b))) };
623 Self(DataPayloadInner::Yoke(yoke))
624 }
625
626 /// Converts a yoked byte buffer into a `DataPayload<BufferMarker>`.
627 pub fn from_yoked_buffer(yoke: Yoke<&'static [u8], Option<Cart>>) -> Self {
628 Self(DataPayloadInner::Yoke(yoke))
629 }
630
631 /// Converts a static byte buffer into a `DataPayload<BufferMarker>`.
632 pub fn from_static_buffer(buffer: &'static [u8]) -> Self {
633 Self(DataPayloadInner::Yoke(Yoke::new_owned(yokeable:buffer)))
634 }
635}
636
637impl<M> Default for DataPayload<M>
638where
639 M: DataMarker,
640 M::Yokeable: Default,
641{
642 fn default() -> Self {
643 Self::from_owned(data:Default::default())
644 }
645}
646
647/// A response object containing an object as payload and metadata about it.
648#[allow(clippy::exhaustive_structs)] // this type is stable
649pub struct DataResponse<M>
650where
651 M: DataMarker,
652{
653 /// Metadata about the returned object.
654 pub metadata: DataResponseMetadata,
655
656 /// The object itself; `None` if it was not loaded.
657 pub payload: Option<DataPayload<M>>,
658}
659
660impl<M> DataResponse<M>
661where
662 M: DataMarker,
663{
664 /// Takes ownership of the underlying payload. Error if not present.
665 ///
666 /// To take the metadata, too, use [`Self::take_metadata_and_payload()`].
667 #[inline]
668 pub fn take_payload(self) -> Result<DataPayload<M>, DataError> {
669 Ok(self.take_metadata_and_payload()?.1)
670 }
671
672 /// Takes ownership of the underlying metadata and payload. Error if payload is not present.
673 #[inline]
674 pub fn take_metadata_and_payload(
675 self,
676 ) -> Result<(DataResponseMetadata, DataPayload<M>), DataError> {
677 Ok((
678 self.metadata,
679 self.payload
680 .ok_or_else(|| DataErrorKind::MissingPayload.with_type_context::<M>())?,
681 ))
682 }
683}
684
685impl<M> TryFrom<DataResponse<M>> for DataPayload<M>
686where
687 M: DataMarker,
688{
689 type Error = DataError;
690
691 fn try_from(response: DataResponse<M>) -> Result<Self, Self::Error> {
692 response.take_payload()
693 }
694}
695
696impl<M> Debug for DataResponse<M>
697where
698 M: DataMarker,
699 for<'a> &'a <M::Yokeable as Yokeable<'a>>::Output: Debug,
700{
701 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
702 write!(
703 f,
704 "DataResponse {{ metadata: {:?}, payload: {:?} }}",
705 self.metadata, self.payload
706 )
707 }
708}
709
710/// Cloning a DataResponse is generally a cheap operation.
711/// See notes in the `Clone` impl for [`Yoke`].
712///
713/// # Examples
714///
715/// ```no_run
716/// use icu_provider::hello_world::*;
717/// use icu_provider::prelude::*;
718///
719/// let resp1: DataResponse<HelloWorldV1Marker> = todo!();
720/// let resp2 = resp1.clone();
721/// ```
722impl<M> Clone for DataResponse<M>
723where
724 M: DataMarker,
725 for<'a> YokeTraitHack<<M::Yokeable as Yokeable<'a>>::Output>: Clone,
726{
727 fn clone(&self) -> Self {
728 Self {
729 metadata: self.metadata.clone(),
730 payload: self.payload.clone(),
731 }
732 }
733}
734
735#[test]
736fn test_debug() {
737 use crate::hello_world::*;
738 use alloc::borrow::Cow;
739 let resp: DataResponse = DataResponse::<HelloWorldV1Marker> {
740 metadata: Default::default(),
741 payload: Some(DataPayload::from_owned(data:HelloWorldV1 {
742 message: Cow::Borrowed("foo"),
743 })),
744 };
745 assert_eq!("DataResponse { metadata: DataResponseMetadata { locale: None, buffer_format: None }, payload: Some(HelloWorldV1 { message: \"foo\" }) }", format!("{resp:?}"));
746}
747