Skip to main content

icu_provider/export/
payload.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 core::any::Any;
6
7use crate::prelude::*;
8use crate::ule::MaybeEncodeAsVarULE;
9use crate::{dynutil::UpcastDataPayload, ule::MaybeAsVarULE};
10use alloc::sync::Arc;
11use databake::{Bake, BakeSize, CrateEnv, TokenStream};
12use yoke::*;
13use zerovec::VarZeroVec;
14use zerovec::vecs::Index32;
15
16#[cfg(doc)]
17use zerovec::ule::VarULE;
18
19trait ExportableDataPayload {
20    fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream;
21    fn bake_size(&self) -> usize;
22    fn serialize_yoke(
23        &self,
24        serializer: &mut dyn erased_serde::Serializer,
25    ) -> Result<(), DataError>;
26    fn maybe_bake_varule_encoded(
27        &self,
28        rest: &[&DataPayload<ExportMarker>],
29        ctx: &CrateEnv,
30    ) -> Option<TokenStream>;
31    fn as_any(&self) -> &dyn Any;
32    fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool;
33}
34
35impl<M: DynamicDataMarker> ExportableDataPayload for DataPayload<M>
36where
37    for<'a> <M::DataStruct as Yokeable<'a>>::Output:
38        Bake + BakeSize + serde::Serialize + MaybeEncodeAsVarULE + PartialEq,
39{
40    fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream {
41        self.get().bake(ctx)
42    }
43
44    fn bake_size(&self) -> usize {
45        size_of::<<M::DataStruct as Yokeable>::Output>() + self.get().borrows_size()
46    }
47
48    fn serialize_yoke(
49        &self,
50        serializer: &mut dyn erased_serde::Serializer,
51    ) -> Result<(), DataError> {
52        use erased_serde::Serialize;
53        self.get()
54            .erased_serialize(serializer)
55            .map_err(|e| DataError::custom("Serde export").with_display_context(&e))?;
56        Ok(())
57    }
58
59    fn maybe_bake_varule_encoded(
60        &self,
61        rest: &[&DataPayload<ExportMarker>],
62        ctx: &CrateEnv,
63    ) -> Option<TokenStream> {
64        let first_varule = self.get().maybe_as_encodeable()?;
65        let recovered_vec: Vec<
66            <<M::DataStruct as Yokeable<'_>>::Output as MaybeEncodeAsVarULE>::EncodeableStruct<'_>,
67        > = core::iter::once(first_varule)
68            .chain(rest.iter().map(|v| {
69                #[expect(clippy::expect_used)] // exporter code
70                v.get()
71                    .payload
72                    .as_any()
73                    .downcast_ref::<Self>()
74                    .expect("payloads expected to be same type")
75                    .get()
76                    .maybe_as_encodeable()
77                    .expect("MaybeEncodeAsVarULE impl should be symmetric")
78            }))
79            .collect();
80        let vzv: VarZeroVec<
81            <<M::DataStruct as Yokeable<'_>>::Output as MaybeAsVarULE>::EncodedStruct,
82            Index32,
83        > = VarZeroVec::from(&recovered_vec);
84        let vzs = vzv.as_slice();
85        Some(vzs.bake(ctx))
86    }
87
88    fn as_any(&self) -> &dyn Any {
89        self
90    }
91
92    fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool {
93        match other.as_any().downcast_ref::<Self>() {
94            Some(downcasted) => (*self).eq(downcasted),
95            None => {
96                debug_assert!(
97                    false,
98                    "cannot compare ExportableDataPayloads of different types: self is {:?} but other is {:?}",
99                    self.type_id(),
100                    other.as_any().type_id(),
101                );
102                false
103            }
104        }
105    }
106}
107
108#[derive(yoke::Yokeable, Clone)]
109#[allow(missing_docs)]
110pub struct ExportBox {
111    payload: Arc<dyn ExportableDataPayload + Sync + Send>,
112}
113
114impl PartialEq for ExportBox {
115    fn eq(&self, other: &Self) -> bool {
116        self.payload.eq_dyn(&*other.payload)
117    }
118}
119
120impl Eq for ExportBox {}
121
122impl core::fmt::Debug for ExportBox {
123    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124        f.debug_struct("ExportBox")
125            .field("payload", &"<payload>")
126            .finish()
127    }
128}
129
130impl<M> UpcastDataPayload<M> for ExportMarker
131where
132    M: DynamicDataMarker,
133    M::DataStruct: Sync + Send,
134    for<'a> <M::DataStruct as Yokeable<'a>>::Output:
135        Bake + BakeSize + serde::Serialize + MaybeEncodeAsVarULE + PartialEq,
136{
137    fn upcast(other: DataPayload<M>) -> DataPayload<ExportMarker> {
138        DataPayload::from_owned(ExportBox {
139            payload: Arc::new(other),
140        })
141    }
142}
143
144impl DataPayload<ExportMarker> {
145    /// Serializes this [`DataPayload`] into a serializer using Serde.
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// use icu_provider::dynutil::UpcastDataPayload;
151    /// use icu_provider::export::*;
152    /// use icu_provider::hello_world::HelloWorldV1;
153    /// use icu_provider::prelude::*;
154    ///
155    /// // Create an example DataPayload
156    /// let payload: DataPayload<HelloWorldV1> = Default::default();
157    /// let export: DataPayload<ExportMarker> = UpcastDataPayload::upcast(payload);
158    ///
159    /// // Serialize the payload to a JSON string
160    /// let mut buffer: Vec<u8> = vec![];
161    /// export
162    ///     .serialize(&mut serde_json::Serializer::new(&mut buffer))
163    ///     .expect("Serialization should succeed");
164    /// assert_eq!(r#"{"message":"(und) Hello World"}"#.as_bytes(), buffer);
165    /// ```
166    pub fn serialize<S>(&self, serializer: S) -> Result<(), DataError>
167    where
168        S: serde::Serializer,
169        S::Ok: 'static, // erased_serde requirement, cannot return values in `Ok`
170    {
171        self.get()
172            .payload
173            .serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(serializer))
174    }
175
176    /// Serializes this [`DataPayload`]'s value into a [`TokenStream`]
177    /// using its [`Bake`] implementations.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use icu_provider::dynutil::UpcastDataPayload;
183    /// use icu_provider::export::*;
184    /// use icu_provider::hello_world::HelloWorldV1;
185    /// use icu_provider::prelude::*;
186    /// # use databake::quote;
187    /// # use std::collections::BTreeSet;
188    ///
189    /// // Create an example DataPayload
190    /// let payload: DataPayload<HelloWorldV1> = Default::default();
191    /// let export: DataPayload<ExportMarker> = UpcastDataPayload::upcast(payload);
192    ///
193    /// let env = databake::CrateEnv::default();
194    /// let tokens = export.tokenize(&env);
195    /// assert_eq!(
196    ///     quote! {
197    ///         icu_provider::hello_world::HelloWorld {
198    ///             message: alloc::borrow::Cow::Borrowed("(und) Hello World"),
199    ///         }
200    ///     }
201    ///     .to_string(),
202    ///     tokens.to_string()
203    /// );
204    /// assert_eq!(
205    ///     env.into_iter().collect::<BTreeSet<_>>(),
206    ///     ["icu_provider", "alloc"]
207    ///         .into_iter()
208    ///         .collect::<BTreeSet<_>>()
209    /// );
210    /// ```
211    pub fn tokenize(&self, ctx: &CrateEnv) -> TokenStream {
212        self.get().payload.bake_yoke(ctx)
213    }
214
215    /// If this payload's struct can be dereferenced as a [`VarULE`],
216    /// returns a [`TokenStream`] of the slice encoded as a [`VarZeroVec`].
217    pub fn tokenize_encoded_seq(structs: &[&Self], ctx: &CrateEnv) -> Option<TokenStream> {
218        let (first, rest) = structs.split_first()?;
219        first.get().payload.maybe_bake_varule_encoded(rest, ctx)
220    }
221
222    /// Returns the data size using postcard encoding
223    pub fn postcard_size(&self) -> usize {
224        use postcard::ser_flavors::{Flavor, Size};
225        let mut serializer = postcard::Serializer {
226            output: Size::default(),
227        };
228        let _infallible = self
229            .get()
230            .payload
231            .serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(&mut serializer));
232
233        serializer.output.finalize().unwrap_or_default()
234    }
235
236    /// Returns an estimate of the baked size, made up of the size of the struct itself,
237    /// as well as the sizes of all its static borrows.
238    ///
239    /// As static borrows are deduplicated by the linker, this is often overcounting.
240    pub fn baked_size(&self) -> usize {
241        self.get().payload.bake_size()
242    }
243}
244
245impl core::hash::Hash for DataPayload<ExportMarker> {
246    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
247        self.hash_and_postcard_size(state);
248    }
249}
250
251impl DataPayload<ExportMarker> {
252    /// Calculates a payload hash and the postcard size
253    pub fn hash_and_postcard_size<H: core::hash::Hasher>(&self, state: &mut H) -> usize {
254        use postcard::ser_flavors::Flavor;
255
256        struct HashFlavor<'a, H>(&'a mut H, usize);
257        impl<H: core::hash::Hasher> Flavor for HashFlavor<'_, H> {
258            type Output = usize;
259
260            fn try_push(&mut self, data: u8) -> postcard::Result<()> {
261                self.0.write_u8(data);
262                self.1 += 1;
263                Ok(())
264            }
265
266            fn finalize(self) -> postcard::Result<Self::Output> {
267                Ok(self.1)
268            }
269        }
270
271        let mut serializer = postcard::Serializer {
272            output: HashFlavor(state, 0),
273        };
274
275        let _infallible = self
276            .get()
277            .payload
278            .serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(&mut serializer));
279
280        serializer.output.1
281    }
282}
283
284/// Marker type for [`ExportBox`].
285#[allow(clippy::exhaustive_structs)] // marker type
286#[derive(Debug)]
287pub struct ExportMarker {}
288
289impl DynamicDataMarker for ExportMarker {
290    type DataStruct = ExportBox;
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use crate::hello_world::*;
297
298    #[test]
299    fn test_compare_with_dyn() {
300        let payload1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
301            message: "abc".into(),
302        });
303        let payload2: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
304            message: "abc".into(),
305        });
306        let payload3: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
307            message: "def".into(),
308        });
309
310        assert!(payload1.eq_dyn(&payload2));
311        assert!(payload2.eq_dyn(&payload1));
312
313        assert!(!payload1.eq_dyn(&payload3));
314        assert!(!payload3.eq_dyn(&payload1));
315    }
316
317    #[test]
318    fn test_export_marker_partial_eq() {
319        let payload1: DataPayload<ExportMarker> =
320            UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
321                message: "abc".into(),
322            }));
323        let payload2: DataPayload<ExportMarker> =
324            UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
325                message: "abc".into(),
326            }));
327        let payload3: DataPayload<ExportMarker> =
328            UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
329                message: "def".into(),
330            }));
331
332        assert_eq!(payload1, payload2);
333        assert_eq!(payload2, payload1);
334        assert_ne!(payload1, payload3);
335        assert_ne!(payload3, payload1);
336    }
337}