// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use core::any::Any; use crate::dynutil::UpcastDataPayload; use crate::prelude::*; use alloc::boxed::Box; use databake::{Bake, CrateEnv, TokenStream}; use yoke::trait_hack::YokeTraitHack; use yoke::*; trait ExportableDataPayload { fn bake_yoke(&self, env: &CrateEnv) -> TokenStream; fn serialize_yoke( &self, serializer: &mut dyn erased_serde::Serializer, ) -> Result<(), DataError>; fn as_any(&self) -> &dyn Any; fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool; } impl ExportableDataPayload for DataPayload where for<'a> >::Output: Bake + serde::Serialize, for<'a> YokeTraitHack<>::Output>: PartialEq, { fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream { self.get().bake(ctx) } fn serialize_yoke( &self, serializer: &mut dyn erased_serde::Serializer, ) -> Result<(), DataError> { use erased_serde::Serialize; self.get() .erased_serialize(serializer) .map_err(|e| DataError::custom("Serde export").with_display_context(&e))?; Ok(()) } fn as_any(&self) -> &dyn Any { self } fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool { match other.as_any().downcast_ref::() { Some(downcasted) => (*self).eq(downcasted), None => { debug_assert!( false, "cannot compare ExportableDataPayloads of different types: self is {:?} but other is {:?}", self.type_id(), other.as_any().type_id(), ); false } } } } #[doc(hidden)] // exposed for make_exportable_provider #[derive(yoke::Yokeable)] pub struct ExportBox { payload: Box, } impl PartialEq for ExportBox { fn eq(&self, other: &Self) -> bool { self.payload.eq_dyn(&*other.payload) } } impl core::fmt::Debug for ExportBox { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ExportBox") .field("payload", &"") .finish() } } impl UpcastDataPayload for ExportMarker where M: DataMarker, M::Yokeable: Sync + Send, for<'a> >::Output: Bake + serde::Serialize, for<'a> YokeTraitHack<>::Output>: PartialEq, { fn upcast(other: DataPayload) -> DataPayload { DataPayload::from_owned(ExportBox { payload: Box::new(other), }) } } impl DataPayload { /// Serializes this [`DataPayload`] into a serializer using Serde. /// /// # Examples /// /// ``` /// use icu_provider::datagen::*; /// use icu_provider::dynutil::UpcastDataPayload; /// use icu_provider::hello_world::HelloWorldV1Marker; /// use icu_provider::prelude::*; /// /// // Create an example DataPayload /// let payload: DataPayload = Default::default(); /// let export: DataPayload = UpcastDataPayload::upcast(payload); /// /// // Serialize the payload to a JSON string /// let mut buffer: Vec = vec![]; /// export /// .serialize(&mut serde_json::Serializer::new(&mut buffer)) /// .expect("Serialization should succeed"); /// assert_eq!(r#"{"message":"(und) Hello World"}"#.as_bytes(), buffer); /// ``` pub fn serialize(&self, serializer: S) -> Result<(), DataError> where S: serde::Serializer, S::Ok: 'static, // erased_serde requirement, cannot return values in `Ok` { self.get() .payload .serialize_yoke(&mut ::erase(serializer)) } /// Serializes this [`DataPayload`]'s value into a [`TokenStream`] /// using its [`Bake`] implementations. /// /// # Examples /// /// ``` /// use icu_provider::datagen::*; /// use icu_provider::dynutil::UpcastDataPayload; /// use icu_provider::hello_world::HelloWorldV1Marker; /// use icu_provider::prelude::*; /// # use databake::quote; /// # use std::collections::BTreeSet; /// /// // Create an example DataPayload /// let payload: DataPayload = Default::default(); /// let export: DataPayload = UpcastDataPayload::upcast(payload); /// /// let env = databake::CrateEnv::default(); /// let tokens = export.tokenize(&env); /// assert_eq!( /// quote! { /// icu_provider::hello_world::HelloWorldV1 { /// message: alloc::borrow::Cow::Borrowed("(und) Hello World"), /// } /// } /// .to_string(), /// tokens.to_string() /// ); /// assert_eq!( /// env.into_iter().collect::>(), /// ["icu_provider", "alloc"] /// .into_iter() /// .collect::>() /// ); /// ``` pub fn tokenize(&self, env: &CrateEnv) -> TokenStream { self.get().payload.bake_yoke(env) } } /// Marker type for [`ExportBox`]. #[allow(clippy::exhaustive_structs)] // marker type #[derive(Debug)] pub struct ExportMarker {} impl DataMarker for ExportMarker { type Yokeable = ExportBox; } #[cfg(test)] mod tests { use super::*; use crate::hello_world::*; #[test] fn test_compare_with_dyn() { let payload1: DataPayload = DataPayload::from_owned(HelloWorldV1 { message: "abc".into(), }); let payload2: DataPayload = DataPayload::from_owned(HelloWorldV1 { message: "abc".into(), }); let payload3: DataPayload = DataPayload::from_owned(HelloWorldV1 { message: "def".into(), }); assert!(payload1.eq_dyn(&payload2)); assert!(payload2.eq_dyn(&payload1)); assert!(!payload1.eq_dyn(&payload3)); assert!(!payload3.eq_dyn(&payload1)); } #[test] fn test_export_marker_partial_eq() { let payload1: DataPayload = UpcastDataPayload::upcast(DataPayload::::from_owned( HelloWorldV1 { message: "abc".into(), }, )); let payload2: DataPayload = UpcastDataPayload::upcast(DataPayload::::from_owned( HelloWorldV1 { message: "abc".into(), }, )); let payload3: DataPayload = UpcastDataPayload::upcast(DataPayload::::from_owned( HelloWorldV1 { message: "def".into(), }, )); assert_eq!(payload1, payload2); assert_eq!(payload2, payload1); assert_ne!(payload1, payload3); assert_ne!(payload3, payload1); } }