From 598573046d43a18eff954b8dd3783a2aac8908a0 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 16:57:41 +0100 Subject: [PATCH 01/10] Add initial Pattern Serde impl --- Cargo.lock | 1 + utils/pattern/Cargo.toml | 2 + utils/pattern/src/common.rs | 11 ++++- utils/pattern/src/frontend/mod.rs | 2 + utils/pattern/src/frontend/serde.rs | 64 +++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 utils/pattern/src/frontend/serde.rs diff --git a/Cargo.lock b/Cargo.lock index 05fbf8a3445..e179a401424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1604,6 +1604,7 @@ version = "0.1.5" dependencies = [ "databake", "displaydoc", + "serde", "writeable", "yoke", "zerofrom", diff --git a/utils/pattern/Cargo.toml b/utils/pattern/Cargo.toml index 463da670918..39c90ffd54c 100644 --- a/utils/pattern/Cargo.toml +++ b/utils/pattern/Cargo.toml @@ -24,6 +24,7 @@ all-features = true displaydoc = { version = "0.2.3", default-features = false } writeable = { workspace = true } databake = { workspace = true, features = ["derive"], optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } yoke = { workspace = true, features = ["derive"], optional = true } zerofrom = { workspace = true, features = ["derive"], optional = true } @@ -35,5 +36,6 @@ zerovec = { workspace = true, features = ["databake", "serde"] } alloc = [] std = ["alloc"] databake = ["dep:databake"] +serde = ["dep:serde"] yoke = ["dep:yoke"] zerofrom = ["dep:zerofrom"] diff --git a/utils/pattern/src/common.rs b/utils/pattern/src/common.rs index 939717e2805..75ac4184177 100644 --- a/utils/pattern/src/common.rs +++ b/utils/pattern/src/common.rs @@ -32,7 +32,7 @@ pub enum PatternItem<'a, T> { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[allow(clippy::exhaustive_enums)] // Part of core data model #[cfg(feature = "alloc")] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum PatternItemCow<'a, T> { /// A placeholder of the type specified on this [`PatternItemCow`]. Placeholder(T), @@ -45,6 +45,15 @@ pub enum PatternItemCow<'a, T> { Literal(Cow<'a, str>), } +impl<'a, T> From> for PatternItemCow<'a, T> { + fn from(value: PatternItem<'a, T>) -> Self { + match value { + PatternItem::Placeholder(t) => Self::Placeholder(t), + PatternItem::Literal(s) => Self::Literal(Cow::Borrowed(s)), + } + } +} + /// Types that implement backing data models for [`Pattern`] implement this trait. /// /// The trait has no public methods and is not implementable outside of this crate. diff --git a/utils/pattern/src/frontend/mod.rs b/utils/pattern/src/frontend/mod.rs index acca9b3866a..f4ff89ed6b3 100644 --- a/utils/pattern/src/frontend/mod.rs +++ b/utils/pattern/src/frontend/mod.rs @@ -4,6 +4,8 @@ #[cfg(feature = "databake")] mod databake; +#[cfg(feature = "serde")] +mod serde; use core::{ fmt::{self, Write}, diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs new file mode 100644 index 00000000000..1222e634a86 --- /dev/null +++ b/utils/pattern/src/frontend/serde.rs @@ -0,0 +1,64 @@ +// 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 super::*; +use alloc::borrow::Cow; + +use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; + +type HumanReadablePatternForSerde<'a, T> = Vec>; + +impl<'de, 'data, B> Deserialize<'de> for Pattern> +where + 'de: 'data, + B: PatternBackend, + B::Store: ToOwned, + ::Owned: Deserialize<'de>, + B::PlaceholderKey: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let pattern_items = + >::deserialize(deserializer)?; + let pattern_owned: Pattern::Owned> = + Pattern::try_from_items(pattern_items.into_iter()) + .map_err(|e| ::custom(e))?; + let pattern_cow: Pattern> = + Pattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); + Ok(pattern_cow) + } else { + let store = Cow::::deserialize(deserializer)?; + let pattern = Self::try_from_store(store) + .map_err(|e| ::custom(e))?; + Ok(pattern) + } + } +} + +impl Serialize for Pattern +where + B: PatternBackend, + B::Store: Serialize, + B::PlaceholderKey: Serialize, + Store: AsRef, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + let pattern_items: HumanReadablePatternForSerde = + B::iter_items(self.store.as_ref()) + .map(|x| x.into()) + .collect(); + pattern_items.serialize(serializer) + } else { + let bytes = self.store.as_ref(); + bytes.serialize(serializer) + } + } +} From 047b552e3be7c14de989cf9fcbe0bd639c5e63ac Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 18:14:54 +0100 Subject: [PATCH 02/10] Add tests --- Cargo.lock | 2 ++ utils/pattern/Cargo.toml | 2 ++ utils/pattern/src/frontend/serde.rs | 36 +++++++++++++++++++++++++++++ utils/pattern/src/single.rs | 1 + 4 files changed, 41 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e179a401424..94483fd05ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1604,7 +1604,9 @@ version = "0.1.5" dependencies = [ "databake", "displaydoc", + "postcard", "serde", + "serde_json", "writeable", "yoke", "zerofrom", diff --git a/utils/pattern/Cargo.toml b/utils/pattern/Cargo.toml index 39c90ffd54c..6f783d8ea37 100644 --- a/utils/pattern/Cargo.toml +++ b/utils/pattern/Cargo.toml @@ -31,6 +31,8 @@ zerofrom = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] zerofrom = { workspace = true, features = ["alloc"] } zerovec = { workspace = true, features = ["databake", "serde"] } +serde_json = { version = "1.0" } +postcard = { version = "1.0", features = ["use-std"] } [features] alloc = [] diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 1222e634a86..ebd55bc9923 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -62,3 +62,39 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::SinglePlaceholderPattern; + + #[test] + fn test_json() { + let pattern_owned = SinglePlaceholderPattern::try_from_str("Hello, {0}!").unwrap(); + let pattern_cow: SinglePlaceholderPattern> = + SinglePlaceholderPattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); + let pattern_json = serde_json::to_string(&pattern_cow).unwrap(); + assert_eq!( + pattern_json, + r#"[{"Literal":"Hello, "},{"Placeholder":"Singleton"},{"Literal":"!"}]"# + ); + let pattern_deserialized: SinglePlaceholderPattern> = + serde_json::from_str(&pattern_json).unwrap(); + assert_eq!(pattern_cow, pattern_deserialized); + } + + #[test] + fn test_postcard() { + let pattern_owned = SinglePlaceholderPattern::try_from_str("Hello, {0}!").unwrap(); + let pattern_cow: SinglePlaceholderPattern> = + SinglePlaceholderPattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); + let pattern_postcard = postcard::to_stdvec(&pattern_cow).unwrap(); + assert_eq!( + pattern_postcard, + b"\x09\x08Hello, !" + ); + let pattern_deserialized: SinglePlaceholderPattern> = + postcard::from_bytes(&pattern_postcard).unwrap(); + assert_eq!(pattern_cow, pattern_deserialized); + } +} diff --git a/utils/pattern/src/single.rs b/utils/pattern/src/single.rs index 0dd57565101..b26b6362942 100644 --- a/utils/pattern/src/single.rs +++ b/utils/pattern/src/single.rs @@ -41,6 +41,7 @@ use alloc::string::String; /// ); /// ``` #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[allow(clippy::exhaustive_enums)] // Singleton pub enum SinglePlaceholderKey { Singleton, From 56fc531f47519764c472c275b22e63c74a76928a Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 18:25:00 +0100 Subject: [PATCH 03/10] Derive test --- utils/pattern/src/frontend/serde.rs | 5 +-- utils/pattern/tests/derive_test.rs | 48 ++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index ebd55bc9923..7fd4402a47f 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -89,10 +89,7 @@ mod tests { let pattern_cow: SinglePlaceholderPattern> = SinglePlaceholderPattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); let pattern_postcard = postcard::to_stdvec(&pattern_cow).unwrap(); - assert_eq!( - pattern_postcard, - b"\x09\x08Hello, !" - ); + assert_eq!(pattern_postcard, b"\x09\x08Hello, !"); let pattern_deserialized: SinglePlaceholderPattern> = postcard::from_bytes(&pattern_postcard).unwrap(); assert_eq!(pattern_cow, pattern_deserialized); diff --git a/utils/pattern/tests/derive_test.rs b/utils/pattern/tests/derive_test.rs index 5adf0de9803..5ad59cdc319 100644 --- a/utils/pattern/tests/derive_test.rs +++ b/utils/pattern/tests/derive_test.rs @@ -11,24 +11,56 @@ use icu_pattern::{Pattern, SinglePlaceholder}; #[cfg_attr(feature = "yoke", derive(yoke::Yokeable))] #[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))] -// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = crate))] -struct DeriveTest_SinglePlaceholderPattern_ZeroVec<'data> { - // #[cfg_attr(feature = "serde", serde(borrow))] - _data: Pattern>, +#[derive(Debug, PartialEq)] +struct DeriveTest_SinglePlaceholderPattern_Cow<'data> { + #[cfg_attr(feature = "serde", serde(borrow))] + data: Pattern>, } #[test] #[cfg(all(feature = "databake", feature = "alloc"))] -fn bake_SinglePlaceholderPattern_ZeroVec() { +fn bake_SinglePlaceholderPattern_Cow() { use databake::*; extern crate std; test_bake!( - DeriveTest_SinglePlaceholderPattern_ZeroVec<'static>, - crate::DeriveTest_SinglePlaceholderPattern_ZeroVec { - _data: icu_pattern::Pattern::::from_store_unchecked( + DeriveTest_SinglePlaceholderPattern_Cow<'static>, + crate::DeriveTest_SinglePlaceholderPattern_Cow { + data: icu_pattern::Pattern::::from_store_unchecked( alloc::borrow::Cow::Borrowed(""), ) }, ); } + +#[test] +#[cfg(feature = "serde")] +fn json_SinglePlaceholderPattern_Cow() { + let pattern_owned = Pattern::::try_from_str("Hello, {0}!").unwrap(); + let pattern_cow: Pattern> = + Pattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); + let data = DeriveTest_SinglePlaceholderPattern_Cow { data: pattern_cow }; + let data_json = serde_json::to_string(&data).unwrap(); + assert_eq!( + data_json, + r#"{"data":[{"Literal":"Hello, "},{"Placeholder":"Singleton"},{"Literal":"!"}]}"# + ); + let data_deserialized: DeriveTest_SinglePlaceholderPattern_Cow = + serde_json::from_str(&data_json).unwrap(); + assert_eq!(data, data_deserialized); +} + +#[test] +#[cfg(feature = "serde")] +fn postcard_SinglePlaceholderPattern_Cow() { + let pattern_owned = Pattern::::try_from_str("Hello, {0}!").unwrap(); + let pattern_cow: Pattern> = + Pattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); + let data = DeriveTest_SinglePlaceholderPattern_Cow { data: pattern_cow }; + let data_postcard = postcard::to_stdvec(&data).unwrap(); + assert_eq!(data_postcard, b"\x09\x08Hello, !"); + let data_deserialized: DeriveTest_SinglePlaceholderPattern_Cow = + postcard::from_bytes(&data_postcard).unwrap(); + assert_eq!(data, data_deserialized); +} From c8471b654ed4554a2c5fe9db9f092bf23ad57a82 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 18:33:32 +0100 Subject: [PATCH 04/10] Clippy --- utils/pattern/src/frontend/serde.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 7fd4402a47f..2d57e054a97 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -26,14 +26,14 @@ where >::deserialize(deserializer)?; let pattern_owned: Pattern::Owned> = Pattern::try_from_items(pattern_items.into_iter()) - .map_err(|e| ::custom(e))?; + .map_err(::custom)?; let pattern_cow: Pattern> = Pattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); Ok(pattern_cow) } else { let store = Cow::::deserialize(deserializer)?; let pattern = Self::try_from_store(store) - .map_err(|e| ::custom(e))?; + .map_err(::custom)?; Ok(pattern) } } From dcf35e26f1e0b1cda4757bef2ba07b97a15b1a3a Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 18:45:31 +0100 Subject: [PATCH 05/10] Generalize Deserialize impl --- utils/pattern/src/frontend/serde.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 2d57e054a97..055673edd18 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -9,13 +9,14 @@ use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; type HumanReadablePatternForSerde<'a, T> = Vec>; -impl<'de, 'data, B> Deserialize<'de> for Pattern> +impl<'de, 'data, B, Store> Deserialize<'de> for Pattern where 'de: 'data, B: PatternBackend, - B::Store: ToOwned, + B::Store: ToOwned + 'de, ::Owned: Deserialize<'de>, B::PlaceholderKey: Deserialize<'de>, + Store: From> + AsRef, { fn deserialize(deserializer: D) -> Result where @@ -27,12 +28,12 @@ where let pattern_owned: Pattern::Owned> = Pattern::try_from_items(pattern_items.into_iter()) .map_err(::custom)?; - let pattern_cow: Pattern> = - Pattern::from_store_unchecked(Cow::Owned(pattern_owned.take_store())); + let pattern_cow: Pattern = + Pattern::from_store_unchecked(Cow::::Owned(pattern_owned.take_store()).into()); Ok(pattern_cow) } else { let store = Cow::::deserialize(deserializer)?; - let pattern = Self::try_from_store(store) + let pattern = Self::try_from_store(store.into()) .map_err(::custom)?; Ok(pattern) } From cf3263b70e73f6808945d69fc2616b4d81719854 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 21:33:15 +0100 Subject: [PATCH 06/10] Features --- utils/pattern/Cargo.toml | 2 +- utils/pattern/src/common.rs | 1 + utils/pattern/src/frontend/serde.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/pattern/Cargo.toml b/utils/pattern/Cargo.toml index 6f783d8ea37..61a96314a93 100644 --- a/utils/pattern/Cargo.toml +++ b/utils/pattern/Cargo.toml @@ -38,6 +38,6 @@ postcard = { version = "1.0", features = ["use-std"] } alloc = [] std = ["alloc"] databake = ["dep:databake"] -serde = ["dep:serde"] +serde = ["alloc", "dep:serde"] yoke = ["dep:yoke"] zerofrom = ["dep:zerofrom"] diff --git a/utils/pattern/src/common.rs b/utils/pattern/src/common.rs index 75ac4184177..ecb030a58a9 100644 --- a/utils/pattern/src/common.rs +++ b/utils/pattern/src/common.rs @@ -45,6 +45,7 @@ pub enum PatternItemCow<'a, T> { Literal(Cow<'a, str>), } +#[cfg(feature = "alloc")] impl<'a, T> From> for PatternItemCow<'a, T> { fn from(value: PatternItem<'a, T>) -> Self { match value { diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 055673edd18..bb35cc37898 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -4,6 +4,7 @@ use super::*; use alloc::borrow::Cow; +use alloc::vec::Vec; use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; From c76856ae08be9291113426e09da955cb2807ea81 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 21:35:37 +0100 Subject: [PATCH 07/10] Fix borrowing in serde impl --- utils/pattern/src/frontend/serde.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index bb35cc37898..696cc4c32ac 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -18,6 +18,7 @@ where ::Owned: Deserialize<'de>, B::PlaceholderKey: Deserialize<'de>, Store: From> + AsRef, + &'de B::Store: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where @@ -33,8 +34,8 @@ where Pattern::from_store_unchecked(Cow::::Owned(pattern_owned.take_store()).into()); Ok(pattern_cow) } else { - let store = Cow::::deserialize(deserializer)?; - let pattern = Self::try_from_store(store.into()) + let store = <&B::Store>::deserialize(deserializer)?; + let pattern = Self::try_from_store(Cow::Borrowed(store).into()) .map_err(::custom)?; Ok(pattern) } @@ -83,6 +84,7 @@ mod tests { let pattern_deserialized: SinglePlaceholderPattern> = serde_json::from_str(&pattern_json).unwrap(); assert_eq!(pattern_cow, pattern_deserialized); + assert!(matches!(pattern_deserialized.take_store(), Cow::Owned(_))); } #[test] @@ -95,5 +97,6 @@ mod tests { let pattern_deserialized: SinglePlaceholderPattern> = postcard::from_bytes(&pattern_postcard).unwrap(); assert_eq!(pattern_cow, pattern_deserialized); + assert!(matches!(pattern_deserialized.take_store(), Cow::Borrowed(_))); } } From a11aad72a9de200e521860d157e41d5fdb0cf04d Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 22:16:06 +0100 Subject: [PATCH 08/10] Change to TryFrom and add another test --- utils/pattern/src/frontend/serde.rs | 91 ++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 696cc4c32ac..31b15983d5d 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -5,38 +5,45 @@ use super::*; use alloc::borrow::Cow; use alloc::vec::Vec; +use core::fmt::Display; use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; -type HumanReadablePatternForSerde<'a, T> = Vec>; +type HumanReadablePattern<'a, B> = Vec::PlaceholderKey>>; impl<'de, 'data, B, Store> Deserialize<'de> for Pattern where 'de: 'data, B: PatternBackend, B::Store: ToOwned + 'de, - ::Owned: Deserialize<'de>, - B::PlaceholderKey: Deserialize<'de>, - Store: From> + AsRef, &'de B::Store: Deserialize<'de>, + B::PlaceholderKey: Deserialize<'de>, + Store: TryFrom> + AsRef, + Store::Error: Display, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { if deserializer.is_human_readable() { - let pattern_items = - >::deserialize(deserializer)?; + let pattern_items = >::deserialize(deserializer)?; let pattern_owned: Pattern::Owned> = Pattern::try_from_items(pattern_items.into_iter()) .map_err(::custom)?; - let pattern_cow: Pattern = - Pattern::from_store_unchecked(Cow::::Owned(pattern_owned.take_store()).into()); - Ok(pattern_cow) + let pattern: Pattern = Pattern::from_store_unchecked( + Cow::::Owned(pattern_owned.take_store()) + .try_into() + .map_err(::custom)?, + ); + Ok(pattern) } else { let store = <&B::Store>::deserialize(deserializer)?; - let pattern = Self::try_from_store(Cow::Borrowed(store).into()) - .map_err(::custom)?; + let pattern = Self::try_from_store( + Cow::Borrowed(store) + .try_into() + .map_err(::custom)?, + ) + .map_err(::custom)?; Ok(pattern) } } @@ -54,10 +61,9 @@ where S: Serializer, { if serializer.is_human_readable() { - let pattern_items: HumanReadablePatternForSerde = - B::iter_items(self.store.as_ref()) - .map(|x| x.into()) - .collect(); + let pattern_items: HumanReadablePattern = B::iter_items(self.store.as_ref()) + .map(|x| x.into()) + .collect(); pattern_items.serialize(serializer) } else { let bytes = self.store.as_ref(); @@ -97,6 +103,59 @@ mod tests { let pattern_deserialized: SinglePlaceholderPattern> = postcard::from_bytes(&pattern_postcard).unwrap(); assert_eq!(pattern_cow, pattern_deserialized); - assert!(matches!(pattern_deserialized.take_store(), Cow::Borrowed(_))); + assert!(matches!( + pattern_deserialized.take_store(), + Cow::Borrowed(_) + )); + } + + macro_rules! check_store { + ($store:expr, $ty:ty) => { + check_store!(@borrow, $store, $ty); + let json = serde_json::to_string::>( + &SinglePlaceholderPattern::from_store_unchecked($store.clone()), + ) + .unwrap(); + let de_json = serde_json::from_str::>(&json).unwrap(); + assert_eq!(de_json.take_store(), $store); + }; + (@borrow, $store:expr, $ty:ty) => { + let postcard = postcard::to_stdvec::>( + &SinglePlaceholderPattern::from_store_unchecked($store.clone()), + ) + .unwrap(); + let de_postcard = postcard::from_bytes::>(&postcard).unwrap(); + assert_eq!(de_postcard.take_store(), $store); + }; + } + + #[test] + fn test_serde_stores() { + let store = SinglePlaceholderPattern::try_from_str("Hello, {0}!") + .unwrap() + .take_store(); + + check_store!(Cow::Borrowed(store.as_str()), Cow); + check_store!(Cow::::Owned(store.clone()), Cow); + check_store!(store.clone(), String); + + #[derive(Debug, Clone, PartialEq, displaydoc::Display)] + struct MyStr<'a>(&'a str); + impl<'a> TryFrom> for MyStr<'a> { + type Error = &'static str; + fn try_from(input: Cow<'a, str>) -> Result, Self::Error> { + match input { + Cow::Borrowed(s) => Ok(MyStr(s)), + Cow::Owned(_) => Err("cannot borrow from a Cow with needed lifetime"), + } + } + } + impl AsRef for MyStr<'_> { + fn as_ref(&self) -> &str { + &self.0 + } + } + + check_store!(@borrow, MyStr(store.as_str()), MyStr); } } From c28d1f491324491068f33acef4de30d39f24f903 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 7 Mar 2024 22:18:43 +0100 Subject: [PATCH 09/10] Additional comment --- utils/pattern/src/frontend/serde.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 31b15983d5d..510320bafb7 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -139,6 +139,7 @@ mod tests { check_store!(Cow::::Owned(store.clone()), Cow); check_store!(store.clone(), String); + /// A type implementing TryFrom> that returns an error if the Cow is Owned #[derive(Debug, Clone, PartialEq, displaydoc::Display)] struct MyStr<'a>(&'a str); impl<'a> TryFrom> for MyStr<'a> { From 38d32144721ae1487927ae6c79da661235ca9246 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Fri, 8 Mar 2024 08:45:55 +0100 Subject: [PATCH 10/10] Clippy --- utils/pattern/src/frontend/serde.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/pattern/src/frontend/serde.rs b/utils/pattern/src/frontend/serde.rs index 510320bafb7..64b84a202a8 100644 --- a/utils/pattern/src/frontend/serde.rs +++ b/utils/pattern/src/frontend/serde.rs @@ -153,7 +153,7 @@ mod tests { } impl AsRef for MyStr<'_> { fn as_ref(&self) -> &str { - &self.0 + self.0 } }