Source code

Revision control

Copy as Markdown

Other Tools

// 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
use core::any::Any;
use crate::prelude::*;
use crate::ule::MaybeEncodeAsVarULE;
use crate::{dynutil::UpcastDataPayload, ule::MaybeAsVarULE};
use alloc::sync::Arc;
use databake::{Bake, BakeSize, CrateEnv, TokenStream};
use yoke::*;
use zerovec::VarZeroVec;
#[cfg(doc)]
use zerovec::ule::VarULE;
trait ExportableDataPayload {
fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream;
fn bake_size(&self) -> usize;
fn serialize_yoke(
&self,
serializer: &mut dyn erased_serde::Serializer,
) -> Result<(), DataError>;
fn maybe_bake_varule_encoded(
&self,
rest: &[&DataPayload<ExportMarker>],
ctx: &CrateEnv,
) -> Option<TokenStream>;
fn as_any(&self) -> &dyn Any;
fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool;
}
impl<M: DynamicDataMarker> ExportableDataPayload for DataPayload<M>
where
for<'a> <M::DataStruct as Yokeable<'a>>::Output:
Bake + BakeSize + serde::Serialize + MaybeEncodeAsVarULE + PartialEq,
{
fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream {
self.get().bake(ctx)
}
fn bake_size(&self) -> usize {
core::mem::size_of::<<M::DataStruct as Yokeable>::Output>() + self.get().borrows_size()
}
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 maybe_bake_varule_encoded(
&self,
rest: &[&DataPayload<ExportMarker>],
ctx: &CrateEnv,
) -> Option<TokenStream> {
let first_varule = self.get().maybe_encode_as_varule()?;
let recovered_vec: Vec<
&<<M::DataStruct as Yokeable<'_>>::Output as MaybeAsVarULE>::EncodedStruct,
> = core::iter::once(first_varule)
.chain(rest.iter().map(|v| {
#[allow(clippy::expect_used)] // exporter code
v.get()
.payload
.as_any()
.downcast_ref::<Self>()
.expect("payloads expected to be same type")
.get()
.maybe_encode_as_varule()
.expect("MaybeEncodeAsVarULE impl should be symmetric")
}))
.collect();
let vzv: VarZeroVec<
<<M::DataStruct as Yokeable<'_>>::Output as MaybeAsVarULE>::EncodedStruct,
> = VarZeroVec::from(&recovered_vec);
let vzs = vzv.as_slice();
Some(vzs.bake(ctx))
}
fn as_any(&self) -> &dyn Any {
self
}
fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool {
match other.as_any().downcast_ref::<Self>() {
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
}
}
}
}
#[derive(yoke::Yokeable, Clone)]
#[allow(missing_docs)]
pub struct ExportBox {
payload: Arc<dyn ExportableDataPayload + Sync + Send>,
}
impl PartialEq for ExportBox {
fn eq(&self, other: &Self) -> bool {
self.payload.eq_dyn(&*other.payload)
}
}
impl Eq for ExportBox {}
impl core::fmt::Debug for ExportBox {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ExportBox")
.field("payload", &"<payload>")
.finish()
}
}
impl<M> UpcastDataPayload<M> for ExportMarker
where
M: DynamicDataMarker,
M::DataStruct: Sync + Send,
for<'a> <M::DataStruct as Yokeable<'a>>::Output:
Bake + BakeSize + serde::Serialize + MaybeEncodeAsVarULE + PartialEq,
{
fn upcast(other: DataPayload<M>) -> DataPayload<ExportMarker> {
DataPayload::from_owned(ExportBox {
payload: Arc::new(other),
})
}
}
impl DataPayload<ExportMarker> {
/// Serializes this [`DataPayload`] into a serializer using Serde.
///
/// # Examples
///
/// ```
/// use icu_provider::dynutil::UpcastDataPayload;
/// use icu_provider::export::*;
/// use icu_provider::hello_world::HelloWorldV1;
/// use icu_provider::prelude::*;
///
/// // Create an example DataPayload
/// let payload: DataPayload<HelloWorldV1> = Default::default();
/// let export: DataPayload<ExportMarker> = UpcastDataPayload::upcast(payload);
///
/// // Serialize the payload to a JSON string
/// let mut buffer: Vec<u8> = 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<S>(&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 <dyn erased_serde::Serializer>::erase(serializer))
}
/// Serializes this [`DataPayload`]'s value into a [`TokenStream`]
/// using its [`Bake`] implementations.
///
/// # Examples
///
/// ```
/// use icu_provider::dynutil::UpcastDataPayload;
/// use icu_provider::export::*;
/// use icu_provider::hello_world::HelloWorldV1;
/// use icu_provider::prelude::*;
/// # use databake::quote;
/// # use std::collections::BTreeSet;
///
/// // Create an example DataPayload
/// let payload: DataPayload<HelloWorldV1> = Default::default();
/// let export: DataPayload<ExportMarker> = UpcastDataPayload::upcast(payload);
///
/// let env = databake::CrateEnv::default();
/// let tokens = export.tokenize(&env);
/// assert_eq!(
/// quote! {
/// icu_provider::hello_world::HelloWorld {
/// message: alloc::borrow::Cow::Borrowed("(und) Hello World"),
/// }
/// }
/// .to_string(),
/// tokens.to_string()
/// );
/// assert_eq!(
/// env.into_iter().collect::<BTreeSet<_>>(),
/// ["icu_provider", "alloc"]
/// .into_iter()
/// .collect::<BTreeSet<_>>()
/// );
/// ```
pub fn tokenize(&self, ctx: &CrateEnv) -> TokenStream {
self.get().payload.bake_yoke(ctx)
}
/// If this payload's struct can be dereferenced as a [`VarULE`],
/// returns a [`TokenStream`] of the slice encoded as a [`VarZeroVec`].
pub fn tokenize_encoded_seq(structs: &[&Self], ctx: &CrateEnv) -> Option<TokenStream> {
let (first, rest) = structs.split_first()?;
first.get().payload.maybe_bake_varule_encoded(rest, ctx)
}
/// Returns the data size using postcard encoding
pub fn postcard_size(&self) -> usize {
use postcard::ser_flavors::{Flavor, Size};
let mut serializer = postcard::Serializer {
output: Size::default(),
};
let _infallible = self
.get()
.payload
.serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(&mut serializer));
serializer.output.finalize().unwrap_or_default()
}
/// Returns an estimate of the baked size, made up of the size of the struct itself,
/// as well as the sizes of all its static borrows.
///
/// As static borrows are deduplicated by the linker, this is often overcounting.
pub fn baked_size(&self) -> usize {
self.get().payload.bake_size()
}
}
impl core::hash::Hash for DataPayload<ExportMarker> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.hash_and_postcard_size(state);
}
}
impl DataPayload<ExportMarker> {
/// Calculates a payload hash and the postcard size
pub fn hash_and_postcard_size<H: core::hash::Hasher>(&self, state: &mut H) -> usize {
use postcard::ser_flavors::Flavor;
struct HashFlavor<'a, H>(&'a mut H, usize);
impl<H: core::hash::Hasher> Flavor for HashFlavor<'_, H> {
type Output = usize;
fn try_push(&mut self, data: u8) -> postcard::Result<()> {
self.0.write_u8(data);
self.1 += 1;
Ok(())
}
fn finalize(self) -> postcard::Result<Self::Output> {
Ok(self.1)
}
}
let mut serializer = postcard::Serializer {
output: HashFlavor(state, 0),
};
let _infallible = self
.get()
.payload
.serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(&mut serializer));
serializer.output.1
}
}
/// Marker type for [`ExportBox`].
#[allow(clippy::exhaustive_structs)] // marker type
#[derive(Debug)]
pub struct ExportMarker {}
impl DynamicDataMarker for ExportMarker {
type DataStruct = ExportBox;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hello_world::*;
#[test]
fn test_compare_with_dyn() {
let payload1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
message: "abc".into(),
});
let payload2: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
message: "abc".into(),
});
let payload3: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
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<ExportMarker> =
UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
message: "abc".into(),
}));
let payload2: DataPayload<ExportMarker> =
UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
message: "abc".into(),
}));
let payload3: DataPayload<ExportMarker> =
UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
message: "def".into(),
}));
assert_eq!(payload1, payload2);
assert_eq!(payload2, payload1);
assert_ne!(payload1, payload3);
assert_ne!(payload3, payload1);
}
}