Skip to content

Commit

Permalink
serde support (#88)
Browse files Browse the repository at this point in the history
Support for serializing `Array<T, U>` as a serde tuple.

Unfortunately `serde` lacks first-class support for arrays, so this is
the best we can do other than a length-prefixed slice-like
serialization, which might also be worth considering.

This seems like the most sensible place to start, though.

Closes #73
  • Loading branch information
tarcieri authored Sep 12, 2024
1 parent 544f75b commit 3df563c
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/hybrid-array.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
targets: ${{ matrix.target }}
- run: cargo build --no-default-features --target ${{ matrix.target }}
- run: cargo build --no-default-features --target ${{ matrix.target }} --features extra-sizes
- run: cargo build --no-default-features --target ${{ matrix.target }} --features serde

careful:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -105,4 +106,5 @@ jobs:
with:
toolchain: ${{ matrix.toolchain }}
- run: cargo test
- run: cargo test --features serde
- run: cargo test --all-features
66 changes: 66 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ rust-version = "1.81"

[dependencies]
typenum = { version = "1.17", features = ["const-generics"] }

# optional dependencies
serde = { version = "1", optional = true, default-features = false }
zeroize = { version = "1.8", optional = true, default-features = false }

[dev-dependencies]
bincode = "1"

[features]
extra-sizes = []

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ mod from_fn;
mod iter;
mod traits;

#[cfg(feature = "serde")]
mod serde;

pub use crate::{iter::TryFromIteratorError, traits::*};
pub use typenum;

Expand Down
105 changes: 105 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! Support for serializing and deserializing `Array` using `serde`.

use crate::{Array, ArraySize};
use core::{fmt, marker::PhantomData};
use serde::{
de::{self, Deserialize, Deserializer, SeqAccess, Visitor},
ser::{Serialize, SerializeTuple, Serializer},
};

impl<'de, T, U> Deserialize<'de> for Array<T, U>
where
T: Deserialize<'de>,
U: ArraySize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
struct ArrayVisitor<T> {
element: PhantomData<T>,
}

impl<'de, T, U> Visitor<'de> for ArrayVisitor<Array<T, U>>
where
T: Deserialize<'de>,
U: ArraySize,
{
type Value = Array<T, U>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "an array of length {}", U::USIZE)
}

fn visit_seq<A>(self, mut seq: A) -> Result<Array<T, U>, A::Error>
where
A: SeqAccess<'de>,
{
Array::<T, U>::try_from_fn(|i| {
seq.next_element()?
.ok_or_else(|| de::Error::invalid_length(i, &self))
})
}
}

let visitor = ArrayVisitor {
element: PhantomData,
};

deserializer.deserialize_tuple(U::USIZE, visitor)
}
}

impl<T, U> Serialize for Array<T, U>
where
T: Serialize,
U: ArraySize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(U::USIZE)?;

for elem in self {
seq.serialize_element(elem)?;
}

seq.end()
}
}

#[cfg(test)]
mod tests {
const INTEGER_ARRAY_EXAMPLE: [u64; 4] = [1, 2, 3, 4];
use crate::{
sizes::{U4, U5},
Array,
};

#[test]
fn deserialize_integer_array() {
let serialized = bincode::serialize(&INTEGER_ARRAY_EXAMPLE).unwrap();
let deserialized: Array<u64, U4> = bincode::deserialize(&serialized).unwrap();
assert_eq!(deserialized, INTEGER_ARRAY_EXAMPLE);
}

#[test]
fn deserialize_too_short() {
let serialized = bincode::serialize(&INTEGER_ARRAY_EXAMPLE).unwrap();
let deserialized: Result<Array<u64, U5>, bincode::Error> =
bincode::deserialize(&serialized);

// TODO(tarcieri): check for more specific error type
assert!(deserialized.is_err())
}

#[test]
fn serialize_integer_array() {
let example: Array<u64, U4> = Array(INTEGER_ARRAY_EXAMPLE);
let serialized = bincode::serialize(&example).unwrap();
let deserialized: Array<u64, U4> = bincode::deserialize(&serialized).unwrap();
assert_eq!(example, deserialized);
}
}

0 comments on commit 3df563c

Please sign in to comment.