summaryrefslogtreecommitdiffstats
path: root/third_party/rust/icu_provider/src/serde
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/icu_provider/src/serde')
-rw-r--r--third_party/rust/icu_provider/src/serde/borrow_de_utils.rs82
-rw-r--r--third_party/rust/icu_provider/src/serde/mod.rs224
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)
+ }
+}