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