diff --git a/.github/workflows/hybrid-array.yml b/.github/workflows/hybrid-array.yml index e2bf9cc..b40faba 100644 --- a/.github/workflows/hybrid-array.yml +++ b/.github/workflows/hybrid-array.yml @@ -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 @@ -105,4 +106,5 @@ jobs: with: toolchain: ${{ matrix.toolchain }} - run: cargo test + - run: cargo test --features serde - run: cargo test --all-features diff --git a/Cargo.lock b/Cargo.lock index cc7526f..a93d420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,86 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "hybrid-array" version = "0.2.0-rc.9" dependencies = [ + "bincode", + "serde", "typenum", "zeroize", ] +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index 056c7e7..f79ed4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [] diff --git a/src/lib.rs b/src/lib.rs index 3488551..b84b198 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..e07acc1 --- /dev/null +++ b/src/serde.rs @@ -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 +where + T: Deserialize<'de>, + U: ArraySize, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + struct ArrayVisitor { + element: PhantomData, + } + + impl<'de, T, U> Visitor<'de> for ArrayVisitor> + where + T: Deserialize<'de>, + U: ArraySize, + { + type Value = Array; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "an array of length {}", U::USIZE) + } + + fn visit_seq(self, mut seq: A) -> Result, A::Error> + where + A: SeqAccess<'de>, + { + Array::::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 Serialize for Array +where + T: Serialize, + U: ArraySize, +{ + fn serialize(&self, serializer: S) -> Result + 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 = 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, 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 = Array(INTEGER_ARRAY_EXAMPLE); + let serialized = bincode::serialize(&example).unwrap(); + let deserialized: Array = bincode::deserialize(&serialized).unwrap(); + assert_eq!(example, deserialized); + } +}