Skip to content

Commit

Permalink
Custom Option<Cow<str>> deserializer
Browse files Browse the repository at this point in the history
  • Loading branch information
robertbastian committed Jan 28, 2022
1 parent 637f985 commit a69941c
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 40 deletions.
28 changes: 6 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ members = [
"tools/benchmark/binsize",
"tools/datagen",
"utils/codepointtrie",
"utils/deduplicating_array",
"utils/serde_utils",
"utils/fixed_decimal",
"utils/litemap",
"utils/pattern",
Expand Down
4 changes: 2 additions & 2 deletions components/datetime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ smallvec = "1.6"
displaydoc = { version = "0.2.3", default-features = false }
either = { version = "1.6.1", default-features = false }
num_enum = { version = "0.5", default_features = false }
serde_with = { version = "1.11.0", default_features = false, optional = true}
serde_utils = { version = "0.5", path = "../../utils/serde_utils", optional = true }

[dev-dependencies]
criterion = "0.3"
Expand All @@ -68,7 +68,7 @@ bench = false # This option is required for Benchmark CI
std = ["icu_provider/std", "icu_locid/std", "icu_calendar/std"]
default = ["provider_serde"]
bench = []
provider_serde = ["serde", "litemap/serde_serialize", "smallvec/serde", "litemap/serde", "zerovec/serde", "tinystr/serde", "serde_with"]
provider_serde = ["serde", "litemap/serde_serialize", "smallvec/serde", "litemap/serde", "zerovec/serde", "tinystr/serde", "serde_utils"]
provider_transform_internals = ["std"]

[[bench]]
Expand Down
8 changes: 4 additions & 4 deletions components/datetime/src/provider/calendar/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ symbols!(
pub struct SymbolsV1<'data>(
#[cfg_attr(
feature = "provider_serde",
serde(borrow, with = "serde_with::As::<[serde_with::BorrowCow; 12]>")
serde(borrow)
)]
pub [Cow<'data, str>; 12],
);
Expand All @@ -107,7 +107,7 @@ symbols!(
pub struct SymbolsV1<'data>(
#[cfg_attr(
feature = "provider_serde",
serde(borrow, with = "serde_with::As::<[serde_with::BorrowCow; 7]>")
serde(borrow)
)]
pub [Cow<'data, str>; 7],
);
Expand All @@ -122,12 +122,12 @@ symbols!(
pub pm: Cow<'data, str>,
#[cfg_attr(
feature = "provider_serde",
serde(borrow, with = "serde_with::As::<Option<serde_with::BorrowCow>>")
serde(borrow, deserialize_with = "serde_utils::option_of_cow::deserialize")
)]
pub noon: Option<Cow<'data, str>>,
#[cfg_attr(
feature = "provider_serde",
serde(borrow, with = "serde_with::As::<Option<serde_with::BorrowCow>>")
serde(borrow, deserialize_with = "serde_utils::option_of_cow::deserialize")
)]
pub midnight: Option<Cow<'data, str>>,
}
Expand Down
4 changes: 2 additions & 2 deletions experimental/list_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ icu_locid = { version = "0.4", path = "../../components/locid" }
icu_provider = { version = "0.4", path = "../../provider/core", features = ["macros"] }
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true }
zerovec = { version = "0.5", path = "../../utils/zerovec", features = ["yoke"] }
deduplicating_array = { version = "0.5", path = "../../utils/deduplicating_array", optional = true }
serde_utils = { version = "0.5", path = "../../utils/serde_utils", optional = true }
regex-automata = { version = "0.1", git = "https://github.com/burntsushi/regex-automata", rev = "d8eee1279fac79514f6e366b6976f97ad7b37174", default-features = false }
writeable = { version = "0.2.1", path = "../../utils/writeable" }

Expand All @@ -43,6 +43,6 @@ path = "src/lib.rs"
[features]
default = ["icu4x_human_readable_de"]
std = ["icu_provider/std", "icu_locid/std", "regex-automata/std"]
provider_serde = ["serde", "zerovec/serde", "deduplicating_array"]
provider_serde = ["serde", "zerovec/serde", "serde_utils"]
provider_transform_internals = ["provider_serde", "std"]
icu4x_human_readable_de = ["provider_serde", "regex-automata/alloc"]
2 changes: 1 addition & 1 deletion experimental/list_formatter/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub mod key {
pub struct ListFormatterPatternsV1<'data>(
#[cfg_attr(
feature = "provider_serde",
serde(borrow, with = "deduplicating_array")
serde(borrow, with = "serde_utils::deduplicating_array")
)]
/// The patterns in the order start, middle, end, pair, short_start, short_middle,
/// short_end, short_pair, narrow_start, narrow_middle, narrow_end, narrow_pair,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

[package]
name = "deduplicating_array"
description = "A serde serialization strategy that uses PartialEq to reduce serialized size."
name = "serde_utils"
description = "Various Serde serialization and deserialization strategies."
version = "0.5.0"
authors = ["The ICU4X Project Developers"]
edition = "2018"
Expand Down Expand Up @@ -34,4 +34,4 @@ bench = []
std = []

[[example]]
name = "postcard"
name = "deduplicate"
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#[derive(serde::Serialize, serde::Deserialize)]
struct DataStruct {
#[serde(with = "deduplicating_array")]
#[serde(with = "serde_utils::deduplicating_array")]
coordinates: [(f64, f64); 5],
}

Expand Down
3 changes: 3 additions & 0 deletions utils/serde_utils/src/array_of_cow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// 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 ).
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//!
//! #[derive(serde::Deserialize, serde::Serialize)]
//! pub struct Foo {
//! #[serde(with = "deduplicating_array")]
//! #[serde(with = "serde_utils::deduplicating_array")]
//! data: [Bar; 12],
//! // ...
//! }
Expand All @@ -28,9 +28,6 @@
//! This implies that singleton integer arrays cannot be used as array elements (they do work in Bincode,
//! but there's really not much point in using them).

#![no_std]
extern crate alloc;

use alloc::fmt::{Error, Formatter};
use alloc::format;
use serde::de::{Deserialize, Deserializer, EnumAccess, Visitor};
Expand Down
6 changes: 6 additions & 0 deletions utils/serde_utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![no_std]
extern crate alloc;

pub mod deduplicating_array;
pub mod array_of_cow;
pub mod option_of_cow;
122 changes: 122 additions & 0 deletions utils/serde_utils/src/option_of_cow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 alloc::fmt;
use alloc::string::String;
use alloc::borrow::ToOwned;

pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Cow<'de, str>>, D::Error>
where
D: serde::de::Deserializer<'de>
{
use serde::de::Error;
use serde::de::Unexpected;

// Note: The following visitor is taken from serde::private::de::borrow_cow_str
struct CowStrVisitor;

impl<'a> serde::de::Visitor<'a> for CowStrVisitor {
type Value = Cow<'a, str>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Cow::Owned(v.to_owned()))
}

fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Cow::Borrowed(v))
}

fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Cow::Owned(v))
}

fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
match alloc::str::from_utf8(v) {
Ok(s) => Ok(Cow::Owned(s.to_owned())),
Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
}
}

fn visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E>
where
E: Error,
{
match alloc::str::from_utf8(v) {
Ok(s) => Ok(Cow::Borrowed(s)),
Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
}
}

fn visit_byte_buf<E>(self, v: alloc::vec::Vec<u8>) -> Result<Self::Value, E>
where
E: Error,
{
match String::from_utf8(v) {
Ok(s) => Ok(Cow::Owned(s)),
Err(e) => Err(Error::invalid_value(
Unexpected::Bytes(&e.into_bytes()),
&self,
)),
}
}
}

struct OptionCowStrVisitor;

impl<'a> serde::de::Visitor<'a> for OptionCowStrVisitor {
type Value = Option<Cow<'a, str>>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string or null")
}

fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
Ok(None)
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::de::Deserializer<'a>,
{
Ok(Some(deserializer.deserialize_str(CowStrVisitor)?))
}
}

deserializer.deserialize_option(OptionCowStrVisitor)
}

#[test]
fn test() {
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct Demo<'s>(
#[serde(borrow, deserialize_with = "deserialize")]
Option<Cow<'s, str>>,
);

let data_orig = Demo(Some("Hello world".into()));
let bytes = postcard::to_stdvec(&data_orig).expect("serialize");
let data_new = postcard::from_bytes::<Demo>(&bytes).expect("deserialize");
assert_eq!(data_orig, data_new);
assert!(matches!(data_new.0, Some(Cow::Borrowed(_))));
}

0 comments on commit a69941c

Please sign in to comment.