From 824651c8cf33caada7d841d139afb8eef0c8a8ca Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Wed, 10 Jul 2024 21:00:47 -0400 Subject: [PATCH] fixed-bytes: add `serde`, document feature flags (#226) * fixed-bytes: add `serde`, document feature flags * manual impl `serde::Deserialize` * add serde tests --- Cargo.lock | 5 ++ net/fixed-bytes/Cargo.toml | 9 +++- net/fixed-bytes/README.md | 10 ++++ net/fixed-bytes/src/lib.rs | 94 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 net/fixed-bytes/README.md diff --git a/Cargo.lock b/Cargo.lock index e5b795e..bab5a38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,6 +274,9 @@ name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -641,6 +644,8 @@ name = "cuprate-fixed-bytes" version = "0.1.0" dependencies = [ "bytes", + "serde", + "serde_json", "thiserror", ] diff --git a/net/fixed-bytes/Cargo.toml b/net/fixed-bytes/Cargo.toml index b592a09..e9985e8 100644 --- a/net/fixed-bytes/Cargo.toml +++ b/net/fixed-bytes/Cargo.toml @@ -6,9 +6,14 @@ license = "MIT" authors = ["Boog900"] [features] -default = ["std"] +default = ["std", "serde"] std = ["bytes/std", "dep:thiserror"] +serde = ["bytes/serde", "dep:serde"] [dependencies] thiserror = { workspace = true, optional = true } -bytes = { workspace = true } \ No newline at end of file +bytes = { workspace = true } +serde = { workspace = true, features = ["derive"], optional = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } \ No newline at end of file diff --git a/net/fixed-bytes/README.md b/net/fixed-bytes/README.md new file mode 100644 index 0000000..b96c9fc --- /dev/null +++ b/net/fixed-bytes/README.md @@ -0,0 +1,10 @@ +# `cuprate-fixed-bytes` +TODO + +# Feature flags +| Feature flag | Does what | +|--------------|-----------| +| `std` | TODO +| `serde` | Enables `serde` on applicable types + +`serde` is enabled by default. \ No newline at end of file diff --git a/net/fixed-bytes/src/lib.rs b/net/fixed-bytes/src/lib.rs index 8776d30..370b881 100644 --- a/net/fixed-bytes/src/lib.rs +++ b/net/fixed-bytes/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + use core::{ fmt::{Debug, Formatter}, ops::{Deref, Index}, @@ -5,7 +7,11 @@ use core::{ use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize}; + #[cfg_attr(feature = "std", derive(thiserror::Error))] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum FixedByteError { #[cfg_attr( feature = "std", @@ -43,8 +49,30 @@ impl Debug for FixedByteError { /// Internally this is just a wrapper around [`Bytes`], with the constructors checking that the length is equal to `N`. /// This implements [`Deref`] with the target being `[u8; N]`. #[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[repr(transparent)] pub struct ByteArray(Bytes); +#[cfg(feature = "serde")] +impl<'de, const N: usize> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Bytes::deserialize(deserializer)?; + let len = bytes.len(); + if len == N { + Ok(Self(bytes)) + } else { + Err(serde::de::Error::invalid_length( + len, + &N.to_string().as_str(), + )) + } + } +} + impl ByteArray { pub fn take_bytes(self) -> Bytes { self.0 @@ -88,8 +116,30 @@ impl TryFrom> for ByteArray { } #[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[repr(transparent)] pub struct ByteArrayVec(Bytes); +#[cfg(feature = "serde")] +impl<'de, const N: usize> Deserialize<'de> for ByteArrayVec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Bytes::deserialize(deserializer)?; + let len = bytes.len(); + if len % N == 0 { + Ok(Self(bytes)) + } else { + Err(serde::de::Error::invalid_length( + len, + &N.to_string().as_str(), + )) + } + } +} + impl ByteArrayVec { pub fn len(&self) -> usize { self.0.len() / N @@ -197,6 +247,8 @@ impl Index for ByteArrayVec { #[cfg(test)] mod tests { + use serde_json::{from_str, to_string}; + use super::*; #[test] @@ -207,4 +259,46 @@ mod tests { assert_eq!(bytes.len(), 100); let _ = bytes[99]; } + + /// Tests that `serde` works on [`ByteArray`]. + #[test] + #[cfg(feature = "serde")] + fn byte_array_serde() { + let b = ByteArray::from([1, 0, 0, 0, 1]); + let string = to_string(&b).unwrap(); + assert_eq!(string, "[1,0,0,0,1]"); + let b2 = from_str::>(&string).unwrap(); + assert_eq!(b, b2); + } + + /// Tests that `serde` works on [`ByteArrayVec`]. + #[test] + #[cfg(feature = "serde")] + fn byte_array_vec_serde() { + let b = ByteArrayVec::from([1, 0, 0, 0, 1]); + let string = to_string(&b).unwrap(); + assert_eq!(string, "[1,0,0,0,1]"); + let b2 = from_str::>(&string).unwrap(); + assert_eq!(b, b2); + } + + /// Tests that bad input `serde` fails on [`ByteArray`]. + #[test] + #[cfg(feature = "serde")] + #[should_panic( + expected = r#"called `Result::unwrap()` on an `Err` value: Error("invalid length 4, expected 5", line: 0, column: 0)"# + )] + fn byte_array_bad_deserialize() { + from_str::>("[1,0,0,0]").unwrap(); + } + + /// Tests that bad input `serde` fails on [`ByteArrayVec`]. + #[test] + #[cfg(feature = "serde")] + #[should_panic( + expected = r#"called `Result::unwrap()` on an `Err` value: Error("invalid length 4, expected 5", line: 0, column: 0)"# + )] + fn byte_array_vec_bad_deserialize() { + from_str::>("[1,0,0,0]").unwrap(); + } }