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 |
12 | pub trait UpcastDataPayload<M> |
13 | where |
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 ] |
66 | macro_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::locale!("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::locale!("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 ] |
168 | macro_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 | } |
257 | |