Skip to content

Commit

Permalink
feat(serde_with): make Bytes adjustable
Browse files Browse the repository at this point in the history
  • Loading branch information
sivizius committed Oct 10, 2024
1 parent e030aa0 commit dd868d2
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 32 deletions.
48 changes: 36 additions & 12 deletions serde_with/src/de/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,10 @@ where
}
}

impl<'de> DeserializeAs<'de, &'de [u8]> for Bytes {
impl<'de, PREFERENCE> DeserializeAs<'de, &'de [u8]> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<&'de [u8], D::Error>
where
D: Deserializer<'de>,
Expand All @@ -1207,7 +1210,10 @@ impl<'de> DeserializeAs<'de, &'de [u8]> for Bytes {
// * visit_str
// * visit_string
#[cfg(feature = "alloc")]
impl<'de> DeserializeAs<'de, Vec<u8>> for Bytes {
impl<'de, PREFERENCE> DeserializeAs<'de, Vec<u8>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
Expand Down Expand Up @@ -1262,12 +1268,15 @@ impl<'de> DeserializeAs<'de, Vec<u8>> for Bytes {
}

#[cfg(feature = "alloc")]
impl<'de> DeserializeAs<'de, Box<[u8]>> for Bytes {
impl<'de, PREFERENCE> DeserializeAs<'de, Box<[u8]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<Box<[u8]>, D::Error>
where
D: Deserializer<'de>,
{
<Bytes as DeserializeAs<'de, Vec<u8>>>::deserialize_as(deserializer)
<Bytes<PREFERENCE> as DeserializeAs<'de, Vec<u8>>>::deserialize_as(deserializer)
.map(Vec::into_boxed_slice)
}
}
Expand All @@ -1284,7 +1293,10 @@ impl<'de> DeserializeAs<'de, Box<[u8]>> for Bytes {
// * visit_string
// * visit_seq
#[cfg(feature = "alloc")]
impl<'de> DeserializeAs<'de, Cow<'de, [u8]>> for Bytes {
impl<'de, PREFERENCE> DeserializeAs<'de, Cow<'de, [u8]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<Cow<'de, [u8]>, D::Error>
where
D: Deserializer<'de>,
Expand Down Expand Up @@ -1354,7 +1366,10 @@ impl<'de> DeserializeAs<'de, Cow<'de, [u8]>> for Bytes {
}
}

impl<'de, const N: usize> DeserializeAs<'de, [u8; N]> for Bytes {
impl<'de, const N: usize, PREFERENCE> DeserializeAs<'de, [u8; N]> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<[u8; N], D::Error>
where
D: Deserializer<'de>,
Expand Down Expand Up @@ -1397,7 +1412,10 @@ impl<'de, const N: usize> DeserializeAs<'de, [u8; N]> for Bytes {
}
}

impl<'de, const N: usize> DeserializeAs<'de, &'de [u8; N]> for Bytes {
impl<'de, const N: usize, PREFERENCE> DeserializeAs<'de, &'de [u8; N]> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<&'de [u8; N], D::Error>
where
D: Deserializer<'de>,
Expand Down Expand Up @@ -1434,7 +1452,10 @@ impl<'de, const N: usize> DeserializeAs<'de, &'de [u8; N]> for Bytes {
}

#[cfg(feature = "alloc")]
impl<'de, const N: usize> DeserializeAs<'de, Cow<'de, [u8; N]>> for Bytes {
impl<'de, const N: usize, PREFERENCE> DeserializeAs<'de, Cow<'de, [u8; N]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<Cow<'de, [u8; N]>, D::Error>
where
D: Deserializer<'de>,
Expand Down Expand Up @@ -1531,12 +1552,15 @@ impl<'de, const N: usize> DeserializeAs<'de, Cow<'de, [u8; N]>> for Bytes {
}

#[cfg(feature = "alloc")]
impl<'de, const N: usize> DeserializeAs<'de, Box<[u8; N]>> for Bytes {
impl<'de, const N: usize, PREFERENCE> DeserializeAs<'de, Box<[u8; N]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn deserialize_as<D>(deserializer: D) -> Result<Box<[u8; N]>, D::Error>
where
D: Deserializer<'de>,
{
Bytes::deserialize_as(deserializer).map(Box::new)
<Bytes<PREFERENCE>>::deserialize_as(deserializer).map(Box::new)
}
}

Expand Down Expand Up @@ -1807,7 +1831,7 @@ impl<'de> DeserializeAs<'de, Cow<'de, [u8]>> for BorrowCow {
where
D: Deserializer<'de>,
{
Bytes::deserialize_as(deserializer)
<Bytes<formats::PreferBytes>>::deserialize_as(deserializer)
}
}

Expand All @@ -1817,7 +1841,7 @@ impl<'de, const N: usize> DeserializeAs<'de, Cow<'de, [u8; N]>> for BorrowCow {
where
D: Deserializer<'de>,
{
Bytes::deserialize_as(deserializer)
<Bytes<formats::PreferBytes>>::deserialize_as(deserializer)
}
}

Expand Down
17 changes: 15 additions & 2 deletions serde_with/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1638,7 +1638,7 @@ pub struct TimestampNanoSecondsWithFrac<
/// };
/// assert_eq!(test, serde_json::from_value(j).unwrap());
///
/// // and serialization will always be a byte sequence
/// // and serialization will default to a byte sequence
/// # assert_eq!(json!(
/// {
/// "from_bytes": [70,111,111,45,66,97,114],
Expand All @@ -1647,7 +1647,20 @@ pub struct TimestampNanoSecondsWithFrac<
/// # ), serde_json::to_value(&test).unwrap());
/// # }
/// ```
pub struct Bytes;
///
/// Often it is prefered to serialize these bytes as string again.
/// Like [`BytesOrString`], this can be adjusted using its generic type parameter,
/// which can be either [`PreferBytes`] (default), [`PreferAsciiString`] or [`PreferString`].
/// The latter two will try to convert arbitrary bytes to a `&str` first and will fallback to
/// serializing as array of bytes only if these bytes would form an invalid string.
/// `PreferAsciiString` will serialize strings containing non-ASCII characters as array as well.
///
/// [`PreferBytes`]: formats::PreferBytes
/// [`PreferAsciiString`]: formats::PreferString
/// [`PreferString`]: formats::PreferString
pub struct Bytes<PREFERENCE: formats::TypePreference = formats::PreferBytes>(
PhantomData<PREFERENCE>,
);

/// Deserialize one or many elements
///
Expand Down
5 changes: 4 additions & 1 deletion serde_with/src/schemars_0_8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,10 @@ where
forward_schema!(Cow<'a, T>);
}

impl<T> JsonSchemaAs<T> for Bytes {
impl<T, PREFERENCE> JsonSchemaAs<T> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
forward_schema!(Vec<u8>);
}

Expand Down
56 changes: 40 additions & 16 deletions serde_with/src/ser/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,80 +772,104 @@ where
}
}

impl SerializeAs<&[u8]> for Bytes {
impl<PREFERENCE> SerializeAs<&[u8]> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &&[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes)
PREFERENCE::serialize_as(*bytes, serializer)
}
}

#[cfg(feature = "alloc")]
impl SerializeAs<Vec<u8>> for Bytes {
impl<PREFERENCE> SerializeAs<Vec<u8>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes)
PREFERENCE::serialize_as(bytes.as_slice(), serializer)
}
}

#[cfg(feature = "alloc")]
impl SerializeAs<Box<[u8]>> for Bytes {
impl<PREFERENCE> SerializeAs<Box<[u8]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &Box<[u8]>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes)
PREFERENCE::serialize_as(bytes.as_ref(), serializer)
}
}

#[cfg(feature = "alloc")]
impl<'a> SerializeAs<Cow<'a, [u8]>> for Bytes {
impl<'a, PREFERENCE> SerializeAs<Cow<'a, [u8]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &Cow<'a, [u8]>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes)
PREFERENCE::serialize_as(bytes.as_ref(), serializer)
}
}

impl<const N: usize> SerializeAs<[u8; N]> for Bytes {
impl<const N: usize, PREFERENCE> SerializeAs<[u8; N]> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes)
PREFERENCE::serialize_as(bytes.as_ref(), serializer)
}
}

impl<const N: usize> SerializeAs<&[u8; N]> for Bytes {
impl<const N: usize, PREFERENCE> SerializeAs<&[u8; N]> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &&[u8; N], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(*bytes)
PREFERENCE::serialize_as(bytes.as_ref(), serializer)
}
}

#[cfg(feature = "alloc")]
impl<const N: usize> SerializeAs<Box<[u8; N]>> for Bytes {
impl<const N: usize, PREFERENCE> SerializeAs<Box<[u8; N]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &Box<[u8; N]>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&**bytes)
PREFERENCE::serialize_as(bytes.as_ref(), serializer)
}
}

#[cfg(feature = "alloc")]
impl<'a, const N: usize> SerializeAs<Cow<'a, [u8; N]>> for Bytes {
impl<'a, const N: usize, PREFERENCE> SerializeAs<Cow<'a, [u8; N]>> for Bytes<PREFERENCE>
where
PREFERENCE: formats::TypePreference,
{
fn serialize_as<S>(bytes: &Cow<'a, [u8; N]>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes.as_ref())
PREFERENCE::serialize_as(bytes.as_ref(), serializer)
}
}

Expand Down
73 changes: 72 additions & 1 deletion serde_with/tests/serde_as/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use expect_test::expect;
use serde::{Deserialize, Serialize};
use serde_with::{
formats::{CommaSeparator, Flexible, PreferAsciiString, PreferString, Strict},
serde_as, BoolFromInt, BytesOrString, DisplayFromStr, IfIsHumanReadable, Map,
serde_as, BoolFromInt, Bytes, BytesOrString, DisplayFromStr, IfIsHumanReadable, Map,
NoneAsEmptyString, OneOrMany, Same, Seq, StringWithSeparator,
};
use std::{
Expand Down Expand Up @@ -1160,6 +1160,77 @@ fn test_bytes() {
);
}

#[test]
fn test_bytes_as_bytes() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S(#[serde_as(as = "Bytes")] Vec<u8>);

is_equal(
S(vec![1, 2, 3]),
expect![[r#"
[
1,
2,
3
]"#]],
);
check_deserialization(S(vec![70, 111, 111, 98, 97, 114]), r#""Foobar""#);
}

#[test]
fn test_bytes_as_string() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S(#[serde_as(as = "Bytes<PreferString>")] Vec<u8>);

is_equal(S(vec![72, 101, 108, 108, 111]), expect![[r#""Hello""#]]);

check_deserialization(S(vec![0xf0, 0x9f, 0xa6, 0xa6]), r#""🦦""#);
is_equal(S(vec![0xf0, 0x9f, 0xa6, 0xa6]), expect![[r#""🦦""#]]);

is_equal(
S(vec![0, 255]),
expect![[r#"
[
0,
255
]"#]],
);
check_deserialization(S(vec![87, 111, 114, 108, 100]), r#""World""#);
}

#[test]
fn test_bytes_as_ascii_string() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S(#[serde_as(as = "Bytes<PreferAsciiString>")] Vec<u8>);

is_equal(S(vec![72, 101, 108, 108, 111]), expect![[r#""Hello""#]]);

check_deserialization(S(vec![0xf0, 0x9f, 0xa6, 0xa6]), r#""🦦""#);
is_equal(
S(vec![0xf0, 0x9f, 0xa6, 0xa6]),
expect![[r#"
[
240,
159,
166,
166
]"#]],
);

is_equal(
S(vec![0, 255]),
expect![[r#"
[
0,
255
]"#]],
);
check_deserialization(S(vec![87, 111, 114, 108, 100]), r#""World""#);
}

#[test]
fn test_one_or_many_prefer_one() {
#[serde_as]
Expand Down

0 comments on commit dd868d2

Please sign in to comment.