icu_provider/
dynutil.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
5//! Utilities for using trait objects with `DataPayload`.
6
7/// Trait to allow conversion from `DataPayload<T>` to `DataPayload<S>`.
8///
9/// This trait can be manually implemented in order to enable [`impl_dynamic_data_provider`](crate::impl_dynamic_data_provider).
10///
11/// [`DataPayload::downcast`]: crate::DataPayload::downcast
12pub trait UpcastDataPayload<M>
13where
14    M: crate::DataMarker,
15    Self: Sized + crate::DataMarker,
16{
17    /// Upcast a `DataPayload<T>` to a `DataPayload<S>` where `T` implements trait `S`.
18    ///
19    /// # Examples
20    ///
21    /// Upcast and then downcast a data struct of type `Cow<str>` (cart type `String`) via
22    /// [`AnyPayload`](crate::any::AnyPayload):
23    ///
24    /// ```
25    /// use icu_provider::dynutil::UpcastDataPayload;
26    /// use icu_provider::hello_world::*;
27    /// use icu_provider::prelude::*;
28    /// use std::borrow::Cow;
29    ///
30    /// let original = DataPayload::<HelloWorldV1Marker>::from_static_str("foo");
31    /// let upcasted = AnyMarker::upcast(original);
32    /// let downcasted = upcasted
33    ///     .downcast::<HelloWorldV1Marker>()
34    ///     .expect("Type conversion");
35    /// assert_eq!(downcasted.get().message, "foo");
36    /// ```
37    fn upcast(other: crate::DataPayload<M>) -> crate::DataPayload<Self>;
38}
39
40/// Implements [`UpcastDataPayload`] from several data markers to a single data marker
41/// that all share the same [`DataMarker::Yokeable`].
42///
43/// # Examples
44///
45/// ```
46/// use icu_provider::prelude::*;
47/// use std::borrow::Cow;
48///
49/// #[icu_provider::data_struct(
50///     FooV1Marker,
51///     BarV1Marker = "demo/bar@1",
52///     BazV1Marker = "demo/baz@1"
53/// )]
54/// pub struct FooV1<'data> {
55///     message: Cow<'data, str>,
56/// };
57///
58/// icu_provider::impl_casting_upcast!(
59///     FooV1Marker,
60///     [BarV1Marker, BazV1Marker,]
61/// );
62/// ```
63///
64/// [`DataMarker::Yokeable`]: crate::DataMarker::Yokeable
65#[macro_export]
66macro_rules! impl_casting_upcast {
67    ($dyn_m:path, [ $($struct_m:ident),+, ]) => {
68        $(
69            impl $crate::dynutil::UpcastDataPayload<$struct_m> for $dyn_m {
70                fn upcast(other: $crate::DataPayload<$struct_m>) -> $crate::DataPayload<$dyn_m> {
71                    other.cast()
72                }
73            }
74        )+
75    }
76}
77
78/// Implements [`DynamicDataProvider`] for a marker type `S` on a type that already implements
79/// [`DynamicDataProvider`] or [`DataProvider`] for one or more `M`, where `M` is a concrete type
80/// that is convertible to `S` via [`UpcastDataPayload`].
81///
82/// Use this macro to add support to your data provider for:
83///
84/// - [`AnyPayload`] if your provider can return typed objects as [`Any`](core::any::Any).
85///
86/// ## Wrapping DataProvider
87///
88/// If your type implements [`DataProvider`], pass a list of markers as the second argument.
89/// This results in a `DynamicDataProvider` that delegates to a specific marker if the key
90/// matches or else returns [`DataErrorKind::MissingDataKey`].
91///
92/// ```
93/// use icu_provider::prelude::*;
94/// use icu_provider::hello_world::*;
95/// #
96/// # // Duplicating HelloWorldProvider because the real one already implements DynamicDataProvider<AnyMarker>
97/// # struct HelloWorldProvider;
98/// # impl DataProvider<HelloWorldV1Marker> for HelloWorldProvider {
99/// #     fn load(
100/// #         &self,
101/// #         req: DataRequest,
102/// #     ) -> Result<DataResponse<HelloWorldV1Marker>, DataError> {
103/// #         icu_provider::hello_world::HelloWorldProvider.load(req)
104/// #     }
105/// # }
106///
107/// // Implement DynamicDataProvider<AnyMarker> on HelloWorldProvider: DataProvider<HelloWorldV1Marker>
108/// icu_provider::impl_dynamic_data_provider!(HelloWorldProvider, [HelloWorldV1Marker,], AnyMarker);
109///
110/// let req = DataRequest {
111///     locale: &icu_locid::langid!("de").into(),
112///     metadata: Default::default(),
113/// };
114///
115/// // Successful because the key matches:
116/// HelloWorldProvider.load_data(HelloWorldV1Marker::KEY, req).unwrap();
117///
118/// // MissingDataKey error as the key does not match:
119/// assert_eq!(
120///     HelloWorldProvider.load_data(icu_provider::data_key!("dummy@1"), req).unwrap_err().kind,
121///     DataErrorKind::MissingDataKey,
122/// );
123/// ```
124///
125/// ## Wrapping DynamicDataProvider
126///
127/// It is also possible to wrap a [`DynamicDataProvider`] to create another [`DynamicDataProvider`]. To do this,
128/// pass a match-like statement for keys as the second argument:
129///
130/// ```
131/// use icu_provider::prelude::*;
132/// use icu_provider::hello_world::*;
133/// #
134/// # struct HelloWorldProvider;
135/// # impl DynamicDataProvider<HelloWorldV1Marker> for HelloWorldProvider {
136/// #     fn load_data(&self, key: DataKey, req: DataRequest)
137/// #             -> Result<DataResponse<HelloWorldV1Marker>, DataError> {
138/// #         icu_provider::hello_world::HelloWorldProvider.load(req)
139/// #     }
140/// # }
141///
142/// // Implement DataProvider<AnyMarker> on HelloWorldProvider: DynamicDataProvider<HelloWorldV1Marker>
143/// icu_provider::impl_dynamic_data_provider!(HelloWorldProvider, {
144///     // Match HelloWorldV1Marker::KEY and delegate to DynamicDataProvider<HelloWorldV1Marker>.
145///     HW = HelloWorldV1Marker::KEY => HelloWorldV1Marker,
146///     // Send the wildcard match also to DynamicDataProvider<HelloWorldV1Marker>.
147///     _ => HelloWorldV1Marker,
148/// }, AnyMarker);
149///
150/// let req = DataRequest {
151///     locale: &icu_locid::langid!("de").into(),
152///     metadata: Default::default(),
153/// };
154///
155/// // Successful because the key matches:
156/// HelloWorldProvider.as_any_provider().load_any(HelloWorldV1Marker::KEY, req).unwrap();
157///
158/// // Because of the wildcard, any key actually works:
159/// HelloWorldProvider.as_any_provider().load_any(icu_provider::data_key!("dummy@1"), req).unwrap();
160/// ```
161///
162/// [`DynamicDataProvider`]: crate::DynamicDataProvider
163/// [`DataProvider`]: crate::DataProvider
164/// [`AnyPayload`]: (crate::any::AnyPayload)
165/// [`DataErrorKind::MissingDataKey`]: (crate::DataErrorKind::MissingDataKey)
166/// [`SerializeMarker`]: (crate::serde::SerializeMarker)
167#[macro_export]
168macro_rules! impl_dynamic_data_provider {
169    // allow passing in multiple things to do and get dispatched
170    ($provider:ty, $arms:tt, $one:path, $($rest:path),+) => {
171        $crate::impl_dynamic_data_provider!(
172            $provider,
173            $arms,
174            $one
175        );
176
177        $crate::impl_dynamic_data_provider!(
178            $provider,
179            $arms,
180            $($rest),+
181        );
182    };
183
184    ($provider:ty, { $($ident:ident = $key:path => $struct_m:ty),+, $(_ => $struct_d:ty,)?}, $dyn_m:ty) => {
185        impl $crate::DynamicDataProvider<$dyn_m> for $provider
186        {
187            fn load_data(
188                &self,
189                key: $crate::DataKey,
190                req: $crate::DataRequest,
191            ) -> Result<
192                $crate::DataResponse<$dyn_m>,
193                $crate::DataError,
194            > {
195                match key.hashed() {
196                    $(
197                        h if h == $key.hashed() => {
198                            let result: $crate::DataResponse<$struct_m> =
199                                $crate::DynamicDataProvider::<$struct_m>::load_data(self, key, req)?;
200                            Ok($crate::DataResponse {
201                                metadata: result.metadata,
202                                payload: result.payload.map(|p| {
203                                    $crate::dynutil::UpcastDataPayload::<$struct_m>::upcast(p)
204                                }),
205                            })
206                        }
207                    )+,
208                    $(
209                        _ => {
210                            let result: $crate::DataResponse<$struct_d> =
211                                $crate::DynamicDataProvider::<$struct_d>::load_data(self, key, req)?;
212                            Ok($crate::DataResponse {
213                                metadata: result.metadata,
214                                payload: result.payload.map(|p| {
215                                    $crate::dynutil::UpcastDataPayload::<$struct_d>::upcast(p)
216                                }),
217                            })
218                        }
219                    )?
220                    _ => Err($crate::DataErrorKind::MissingDataKey.with_req(key, req))
221                }
222            }
223        }
224
225    };
226    ($provider:ty, [ $($(#[$cfg:meta])? $struct_m:ty),+, ], $dyn_m:path) => {
227        impl $crate::DynamicDataProvider<$dyn_m> for $provider
228        {
229            fn load_data(
230                &self,
231                key: $crate::DataKey,
232                req: $crate::DataRequest,
233            ) -> Result<
234                $crate::DataResponse<$dyn_m>,
235                $crate::DataError,
236            > {
237                match key.hashed() {
238                    $(
239                        $(#[$cfg])?
240                        h if h == <$struct_m>::KEY.hashed() => {
241                            let result: $crate::DataResponse<$struct_m> =
242                                $crate::DataProvider::load(self, req)?;
243                            Ok($crate::DataResponse {
244                                metadata: result.metadata,
245                                payload: result.payload.map(|p| {
246                                    $crate::dynutil::UpcastDataPayload::<$struct_m>::upcast(p)
247                                }),
248                            })
249                        }
250                    )+,
251                    _ => Err($crate::DataErrorKind::MissingDataKey.with_req(key, req))
252                }
253            }
254        }
255    };
256}