Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Bytes type for more efficient byte sequences #277

Merged
merged 4 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
},
```

* The `Bytes` type is heavily inspired by `serde_bytes` and ports it to the `serde_as` system.

```rust
#[serde_as(as = "Bytes")]
value: Vec<u8>,
```

Compared to `serde_bytes` these improvements are available

1. Integration with the `serde_as` annotation (see [serde-bytes#14][serde-bytes-complex]).
2. Implementation for arrays of arbitrary size (Rust 1.51+) (see [serde-bytes#26][serde-bytes-arrays]).

[serde-bytes-complex]: https://github.com/serde-rs/bytes/issues/14
[serde-bytes-arrays]: https://github.com/serde-rs/bytes/issues/26

## [1.6.4] - 2021-02-16

### Fixed
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ ron = "0.6"
serde-xml-rs = "0.4.1"
serde_derive = "1.0.75"
serde_json = {version = "1.0.25", features = ["preserve_order"]}
serde_test = "1.0.124"
version-sync = "0.9.1"

[[test]]
Expand Down
53 changes: 53 additions & 0 deletions src/de/const_arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::*;
use crate::utils::{MapIter, SeqIter};
use serde::de::*;
use std::collections::{BTreeMap, HashMap};
use std::convert::TryInto;
use std::fmt;
use std::mem::MaybeUninit;

Expand Down Expand Up @@ -146,3 +147,55 @@ macro_rules! tuple_seq_as_map_impl_intern {
}
tuple_seq_as_map_impl_intern!([(K, V); N], BTreeMap<KAs, VAs>);
tuple_seq_as_map_impl_intern!([(K, V); N], HashMap<KAs, VAs>);

impl<'de, const N: usize> DeserializeAs<'de, [u8; N]> for Bytes {
fn deserialize_as<D>(deserializer: D) -> Result<[u8; N], D::Error>
where
D: Deserializer<'de>,
{
struct ArrayVisitor<const M: usize>;

impl<'de, const M: usize> Visitor<'de> for ArrayVisitor<M> {
type Value = [u8; M];

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_fmt(format_args!("an byte array of size {}", M))
}

fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
array_from_iterator(SeqIter::new(seq), &self)
}

fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
v.try_into()
.map_err(|_| Error::invalid_length(v.len(), &self))
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
v.as_bytes()
.try_into()
.map_err(|_| Error::invalid_length(v.len(), &self))
}
}

deserializer.deserialize_bytes(ArrayVisitor::<N>)
}
}

impl<'de, const N: usize> DeserializeAs<'de, Box<[u8; N]>> for Bytes {
fn deserialize_as<D>(deserializer: D) -> Result<Box<[u8; N]>, D::Error>
where
D: Deserializer<'de>,
{
<Bytes as DeserializeAs<'de, [u8; N]>>::deserialize_as(deserializer).map(Box::new)
}
}
164 changes: 164 additions & 0 deletions src/de/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::rust::StringWithSeparator;
use crate::utils;
use crate::utils::duration::DurationSigned;
use serde::de::*;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use std::convert::From;
use std::fmt::{self, Display};
Expand Down Expand Up @@ -702,3 +703,166 @@ where
Ok(Option::<U>::deserialize_as(deserializer)?.unwrap_or_default())
}
}

impl<'de> DeserializeAs<'de, &'de [u8]> for Bytes {
fn deserialize_as<D>(deserializer: D) -> Result<&'de [u8], D::Error>
where
D: Deserializer<'de>,
{
<&'de [u8]>::deserialize(deserializer)
}
}

// serde_bytes implementation for ByteBuf
// https://github.com/serde-rs/bytes/blob/cbae606b9dc225fc094b031cc84eac9493da2058/src/bytebuf.rs#L196
//
// Implements:
// * visit_seq
// * visit_bytes
// * visit_byte_buf
// * visit_str
// * visit_string
impl<'de> DeserializeAs<'de, Vec<u8>> for Bytes {
fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
struct VecVisitor;

impl<'de> Visitor<'de> for VecVisitor {
type Value = Vec<u8>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a byte array")
}

fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
utils::SeqIter::new(seq).collect::<Result<_, _>>()
}

fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v.to_vec())
}

fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v)
}

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

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

deserializer.deserialize_byte_buf(VecVisitor)
}
}

impl<'de> DeserializeAs<'de, Box<[u8]>> for Bytes {
fn deserialize_as<D>(deserializer: D) -> Result<Box<[u8]>, D::Error>
where
D: Deserializer<'de>,
{
<Bytes as DeserializeAs<'de, Vec<u8>>>::deserialize_as(deserializer)
.map(|vec| vec.into_boxed_slice())
}
}

// serde_bytes implementation for Cow<'a, [u8]>
// https://github.com/serde-rs/bytes/blob/cbae606b9dc225fc094b031cc84eac9493da2058/src/de.rs#L77
//
// Implements:
// * visit_borrowed_bytes
// * visit_borrowed_str
// * visit_bytes
// * visit_str
// * visit_byte_buf
// * visit_string
// * visit_seq
impl<'de> DeserializeAs<'de, Cow<'de, [u8]>> for Bytes {
fn deserialize_as<D>(deserializer: D) -> Result<Cow<'de, [u8]>, D::Error>
where
D: Deserializer<'de>,
{
struct CowVisitor;

impl<'de> Visitor<'de> for CowVisitor {
type Value = Cow<'de, [u8]>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a byte array")
}

fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Cow::Borrowed(v))
}

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

fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Cow::Owned(v.to_vec()))
}

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

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

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

fn visit_seq<V>(self, seq: V) -> Result<Self::Value, V::Error>
where
V: SeqAccess<'de>,
{
Ok(Cow::Owned(
utils::SeqIter::new(seq).collect::<Result<_, _>>()?,
))
}
}

deserializer.deserialize_bytes(CowVisitor)
}
}
39 changes: 28 additions & 11 deletions src/guide/serde_as.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ The basic design of the system was done by [@markazmierczak](https://github.com/
5. [Re-exporting `serde_as`](#re-exporting-serde_as)
2. [De/Serialize Implementations Available](#deserialize-implementations-available)
1. [Big Array support (Rust 1.51+)](#big-array-support-rust-151)
2. [Bytes / `Vec<u8>` to hex string](#bytes--vecu8-to-hex-string)
3. [`Default` from `null`](#default-from-null)
4. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
5. [`Duration` as seconds](#duration-as-seconds)
6. [Ignore deserialization errors](#ignore-deserialization-errors)
7. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
8. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
9. [`None` as empty `String`](#none-as-empty-string)
10. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
11. [Value into JSON String](#value-into-json-string)
12. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
2. [`Bytes` with more efficiency](#bytes-with-more-efficiency)
3. [Bytes / `Vec<u8>` to hex string](#bytes--vecu8-to-hex-string)
4. [`Default` from `null`](#default-from-null)
5. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
6. [`Duration` as seconds](#duration-as-seconds)
7. [Ignore deserialization errors](#ignore-deserialization-errors)
8. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
9. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
10. [`None` as empty `String`](#none-as-empty-string)
11. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
12. [Value into JSON String](#value-into-json-string)
13. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)

## Switching from serde's with to `serde_as`

Expand Down Expand Up @@ -300,6 +301,21 @@ value: [[u8; 64]; 33],
"value": [[0,0,0,0,0,...], [0,0,0,...], ...],
```

### `Bytes` with more efficiency

[`Bytes`]

More efficient serialization for byte slices and similar.

```ignore
// Rust
#[serde_as(as = "Bytes")]
value: Vec<u8>,

// JSON
"value": [0, 1, 2, 3, ...],
```

### Bytes / `Vec<u8>` to hex string

[`Hex`]
Expand Down Expand Up @@ -516,6 +532,7 @@ This includes `BinaryHeap<(K, V)>`, `BTreeSet<(K, V)>`, `HashSet<(K, V)>`, `Link

The [inverse operation](#maps-to-vec-of-tuples) is also available.

[`Bytes`]: crate::Bytes
[`chrono::DateTime<Local>`]: chrono_crate::DateTime
[`chrono::DateTime<Utc>`]: chrono_crate::DateTime
[`chrono::Duration`]: https://docs.rs/chrono/latest/chrono/struct.Duration.html
Expand Down
Loading