diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/icu_provider/src/serde/borrow_de_utils.rs | 82 | ||||
-rw-r--r-- | third_party/rust/icu_provider/src/serde/mod.rs | 224 |
2 files changed, 306 insertions, 0 deletions
diff --git a/third_party/rust/icu_provider/src/serde/borrow_de_utils.rs b/third_party/rust/icu_provider/src/serde/borrow_de_utils.rs new file mode 100644 index 0000000000..d614bc9ecb --- /dev/null +++ b/third_party/rust/icu_provider/src/serde/borrow_de_utils.rs @@ -0,0 +1,82 @@ +// 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 alloc::borrow::Cow; +use serde::de::Deserializer; +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(transparent)] +// Cows fail to borrow in some situations (array, option), but structs of Cows don't. +#[allow(clippy::exhaustive_structs)] // newtype +#[derive(Debug)] +pub struct CowWrap<'data>(#[serde(borrow)] pub Cow<'data, str>); + +#[derive(Deserialize)] +#[serde(transparent)] +// Cows fail to borrow in some situations (array, option), but structs of Cows don't. +#[allow(clippy::exhaustive_structs)] // newtype +#[derive(Debug)] +pub struct CowBytesWrap<'data>(#[serde(borrow)] pub Cow<'data, [u8]>); + +pub fn array_of_cow<'de, D, const N: usize>(deserializer: D) -> Result<[Cow<'de, str>; N], D::Error> +where + D: Deserializer<'de>, + [CowWrap<'de>; N]: Deserialize<'de>, +{ + <[CowWrap<'de>; N]>::deserialize(deserializer).map(|array| array.map(|wrap| wrap.0)) +} + +pub fn option_of_cow<'de, D>(deserializer: D) -> Result<Option<Cow<'de, str>>, D::Error> +where + D: Deserializer<'de>, +{ + <Option<CowWrap<'de>>>::deserialize(deserializer).map(|opt| opt.map(|wrap| wrap.0)) +} + +pub fn tuple_of_cow<'de, D>(deserializer: D) -> Result<(Cow<'de, str>, Cow<'de, str>), D::Error> +where + D: Deserializer<'de>, + (CowWrap<'de>, CowWrap<'de>): Deserialize<'de>, +{ + <(CowWrap<'de>, CowWrap<'de>)>::deserialize(deserializer).map(|x| (x.0 .0, x.1 .0)) +} + +#[test] +fn test_option() { + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct Demo<'s>(#[serde(borrow, deserialize_with = "option_of_cow")] Option<Cow<'s, str>>); + + let data_orig = Demo(Some("Hello world".into())); + let json = serde_json::to_string(&data_orig).expect("serialize"); + let data_new = serde_json::from_str::<Demo>(&json).expect("deserialize"); + assert_eq!(data_orig, data_new); + assert!(matches!(data_new.0, Some(Cow::Borrowed(_)))); +} + +#[test] +fn test_tuple() { + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct Demo<'s>( + #[serde(borrow, deserialize_with = "tuple_of_cow")] (Cow<'s, str>, Cow<'s, str>), + ); + + let data_orig = Demo(("Hello world".into(), "Hello earth".into())); + let json = serde_json::to_string(&data_orig).expect("serialize"); + let data_new = serde_json::from_str::<Demo>(&json).expect("deserialize"); + assert_eq!(data_orig, data_new); + assert!(matches!(data_new.0, (Cow::Borrowed(_), Cow::Borrowed(_)))); +} + +#[test] +fn test_array() { + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct Demo<'s>(#[serde(borrow, deserialize_with = "array_of_cow")] [Cow<'s, str>; 1]); + + let data_orig = Demo(["Hello world".into()]); + let json = serde_json::to_string(&data_orig).expect("serialize"); + let data_new = serde_json::from_str::<Demo>(&json).expect("deserialize"); + assert_eq!(data_orig, data_new); + assert!(matches!(data_new.0, [Cow::Borrowed(_)])); +} diff --git a/third_party/rust/icu_provider/src/serde/mod.rs b/third_party/rust/icu_provider/src/serde/mod.rs new file mode 100644 index 0000000000..edd827c312 --- /dev/null +++ b/third_party/rust/icu_provider/src/serde/mod.rs @@ -0,0 +1,224 @@ +// 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 ). + +//! Provides the [`DeserializingBufferProvider`] wrapper, which deserializes data using Serde. +//! +//! Providers that produce opaque buffers that need to be deserialized into concrete data structs, +//! such as `FsDataProvider`, should implement [`BufferProvider`]. These can be converted into +//! [`DeserializingBufferProvider`] using the [`as_deserializing`](AsDeserializingBufferProvider::as_deserializing) +//! convenience method. +//! +//! [`BufferProvider`]: crate::buf::BufferProvider + +// Hidden for now, but could be made public-stable in the future. +#[doc(hidden)] +pub mod borrow_de_utils; + +use crate::buf::BufferFormat; +use crate::buf::BufferProvider; +use crate::prelude::*; +use serde::de::Deserialize; +use yoke::trait_hack::YokeTraitHack; +use yoke::Yokeable; + +/// A [`BufferProvider`] that deserializes its data using Serde. +#[derive(Debug)] +pub struct DeserializingBufferProvider<'a, P: ?Sized>(&'a P); + +/// Blanket-implemented trait adding the [`Self::as_deserializing()`] function. +pub trait AsDeserializingBufferProvider { + /// Wrap this [`BufferProvider`] in a [`DeserializingBufferProvider`]. + /// + /// This requires enabling the deserialization Cargo feature + /// for the expected format(s): + /// + /// - `deserialize_json` + /// - `deserialize_postcard_1` + /// - `deserialize_bincode_1` + fn as_deserializing(&self) -> DeserializingBufferProvider<Self>; +} + +impl<P> AsDeserializingBufferProvider for P +where + P: BufferProvider + ?Sized, +{ + /// Wrap this [`BufferProvider`] in a [`DeserializingBufferProvider`]. + /// + /// This requires enabling the deserialization Cargo feature + /// for the expected format(s): + /// + /// - `deserialize_json` + /// - `deserialize_postcard_1` + /// - `deserialize_bincode_1` + fn as_deserializing(&self) -> DeserializingBufferProvider<Self> { + DeserializingBufferProvider(self) + } +} + +fn deserialize_impl<'data, M>( + // Allow `bytes` to be unused in case all buffer formats are disabled + #[allow(unused_variables)] bytes: &'data [u8], + buffer_format: BufferFormat, +) -> Result<<M::Yokeable as Yokeable<'data>>::Output, DataError> +where + M: DataMarker, + // Actual bound: + // for<'de> <M::Yokeable as Yokeable<'de>>::Output: Deserialize<'de>, + // Necessary workaround bound (see `yoke::trait_hack` docs): + for<'de> YokeTraitHack<<M::Yokeable as Yokeable<'de>>::Output>: Deserialize<'de>, +{ + match buffer_format { + #[cfg(feature = "deserialize_json")] + BufferFormat::Json => { + let mut d = serde_json::Deserializer::from_slice(bytes); + let data = YokeTraitHack::<<M::Yokeable as Yokeable>::Output>::deserialize(&mut d)?; + Ok(data.0) + } + + #[cfg(feature = "deserialize_bincode_1")] + BufferFormat::Bincode1 => { + use bincode::Options; + let options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes(); + let mut d = bincode::de::Deserializer::from_slice(bytes, options); + let data = YokeTraitHack::<<M::Yokeable as Yokeable>::Output>::deserialize(&mut d)?; + Ok(data.0) + } + + #[cfg(feature = "deserialize_postcard_1")] + BufferFormat::Postcard1 => { + let mut d = postcard::Deserializer::from_bytes(bytes); + let data = YokeTraitHack::<<M::Yokeable as Yokeable>::Output>::deserialize(&mut d)?; + Ok(data.0) + } + + // Allowed for cases in which all features are enabled + #[allow(unreachable_patterns)] + _ => Err(DataErrorKind::UnavailableBufferFormat(buffer_format).into_error()), + } +} + +impl DataPayload<BufferMarker> { + /// Deserialize a [`DataPayload`]`<`[`BufferMarker`]`>` into a [`DataPayload`] of a + /// specific concrete type. + /// + /// This requires enabling the deserialization Cargo feature + /// for the expected format(s): + /// + /// - `deserialize_json` + /// - `deserialize_postcard_1` + /// - `deserialize_bincode_1` + /// + /// This function takes the buffer format as an argument. When a buffer payload is returned + /// from a data provider, the buffer format is stored in the [`DataResponseMetadata`]. + /// + /// # Examples + /// + /// Requires the `deserialize_json` Cargo feature: + /// + /// ``` + /// use icu_provider::buf::BufferFormat; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// + /// let buffer: &[u8] = br#"{"message":"Hallo Welt"}"#; + /// + /// let buffer_payload = DataPayload::from_owned(buffer); + /// let payload: DataPayload<HelloWorldV1Marker> = buffer_payload + /// .into_deserialized(BufferFormat::Json) + /// .expect("Deserialization successful"); + /// + /// assert_eq!(payload.get().message, "Hallo Welt"); + /// ``` + pub fn into_deserialized<M>( + self, + buffer_format: BufferFormat, + ) -> Result<DataPayload<M>, DataError> + where + M: DataMarker, + // Actual bound: + // for<'de> <M::Yokeable as Yokeable<'de>>::Output: Deserialize<'de>, + // Necessary workaround bound (see `yoke::trait_hack` docs): + for<'de> YokeTraitHack<<M::Yokeable as Yokeable<'de>>::Output>: Deserialize<'de>, + { + self.try_map_project(|bytes, _| deserialize_impl::<M>(bytes, buffer_format)) + } +} + +impl<P, M> DynamicDataProvider<M> for DeserializingBufferProvider<'_, P> +where + M: DataMarker, + P: BufferProvider + ?Sized, + // Actual bound: + // for<'de> <M::Yokeable as Yokeable<'de>>::Output: serde::de::Deserialize<'de>, + // Necessary workaround bound (see `yoke::trait_hack` docs): + for<'de> YokeTraitHack<<M::Yokeable as Yokeable<'de>>::Output>: Deserialize<'de>, +{ + /// Converts a buffer into a concrete type by deserializing from a supported buffer format. + /// + /// This requires enabling the deserialization Cargo feature + /// for the expected format(s): + /// + /// - `deserialize_json` + /// - `deserialize_postcard_1` + /// - `deserialize_bincode_1` + fn load_data(&self, key: DataKey, req: DataRequest) -> Result<DataResponse<M>, DataError> { + let buffer_response = BufferProvider::load_buffer(self.0, key, req)?; + let buffer_format = buffer_response.metadata.buffer_format.ok_or_else(|| { + DataError::custom("BufferProvider didn't set BufferFormat").with_req(key, req) + })?; + Ok(DataResponse { + metadata: buffer_response.metadata, + payload: buffer_response + .payload + .map(|p| p.into_deserialized(buffer_format)) + .transpose() + .map_err(|e| e.with_req(key, req))?, + }) + } +} + +impl<P, M> DataProvider<M> for DeserializingBufferProvider<'_, P> +where + M: KeyedDataMarker, + P: BufferProvider + ?Sized, + // Actual bound: + // for<'de> <M::Yokeable as Yokeable<'de>>::Output: Deserialize<'de>, + // Necessary workaround bound (see `yoke::trait_hack` docs): + for<'de> YokeTraitHack<<M::Yokeable as Yokeable<'de>>::Output>: Deserialize<'de>, +{ + /// Converts a buffer into a concrete type by deserializing from a supported buffer format. + /// + /// This requires enabling the deserialization Cargo feature + /// for the expected format(s): + /// + /// - `deserialize_json` + /// - `deserialize_postcard_1` + /// - `deserialize_bincode_1` + fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> { + self.load_data(M::KEY, req) + } +} + +#[cfg(feature = "deserialize_json")] +impl From<serde_json::error::Error> for crate::DataError { + fn from(e: serde_json::error::Error) -> Self { + crate::DataError::custom("JSON deserialize").with_display_context(&e) + } +} + +#[cfg(feature = "deserialize_bincode_1")] +impl From<bincode::Error> for crate::DataError { + fn from(e: bincode::Error) -> Self { + crate::DataError::custom("Bincode deserialize").with_display_context(&e) + } +} + +#[cfg(feature = "deserialize_postcard_1")] +impl From<postcard::Error> for crate::DataError { + fn from(e: postcard::Error) -> Self { + crate::DataError::custom("Postcard deserialize").with_display_context(&e) + } +} |