diff --git a/Cargo.toml b/Cargo.toml index 2f4f5c9..453c390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde-indexed" -version = "0.0.1" +version = "0.0.2" authors = ["Nicolas Stalder "] license = "Apache-2.0 OR MIT" description = "Derivation of Serialize and Deserialize that replaces struct keys with numerical indices." diff --git a/README.md b/README.md index bbe1fea..f9b26ad 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ ## serde-indexed +[![crates.io][crates-image]][crates-link] +[![Documentation][docs-image]][docs-link] + Derivation of [`Serialize`][serialize] and [`Deserialize`][deserialize] that replaces struct keys with numerical indices. Primary use case is to handle [CTAP CBOR][ctap-cbor] messages, in particular support for: @@ -23,6 +26,10 @@ This was a nice opportunity to learn proc-macros, I roughly followed [`serde-rep To see some generated code, run `cargo expand --test basics`. +[crates-image]: https://img.shields.io/crates/v/serde-indexed.svg?style=flat-square +[crates-link]: https://crates.io/crates/serde-indexed +[docs-image]: https://img.shields.io/badge/docs.rs-api-green?style=flat-square +[docs-link]: https://docs.rs/serde-indexed [serialize]: https://docs.serde.rs/serde/ser/trait.Serialize.html [deserialize]: https://docs.serde.rs/serde/de/trait.Deserialize.html [ctap-cbor]: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ctap2-canonical-cbor-encoding-form diff --git a/src/lib.rs b/src/lib.rs index 43af9df..f0a189a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ /*! Derivation of [`Serialize`][serialize] and [`Deserialize`][deserialize] that replaces struct keys with numerical indices. ### Usage example -The macros currently understand `serde`'s [`skip_if_serialized`][skip-serializing-if] field attribute +The macros currently understand `serde`'s [`skip_serializing_if`][skip-serializing-if] field attribute and a custom `offset` container attribute. ``` @@ -31,7 +31,7 @@ extern crate proc_macro; mod parse; use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::parse_macro_input; use crate::parse::Input; @@ -69,10 +69,10 @@ fn count_serialized_fields(fields: &[parse::Field]) -> Vec { - quote! { + if #path(&self.#member) { 0 } else { 1 } } + quote! { if #path(&self.#member) { 0 } else { 1 } } } None => { - quote! { + 1 } + quote! { 1 } } } }) @@ -93,9 +93,7 @@ pub fn derive_serialize(input: TokenStream) -> TokenStream { S: serde::Serializer { use serde::ser::SerializeMap; - let num_fields = 0 - #(#num_fields)* - ; + let num_fields = 0 #( + #num_fields)*; let mut map = serializer.serialize_map(Some(num_fields))?; #(#serialize_fields)* @@ -110,7 +108,7 @@ fn none_fields(fields: &[parse::Field]) -> Vec { fields .iter() .map(|field| { - let ident = syn::Ident::new(&field.label, proc_macro2::Span::call_site()); + let ident = format_ident!("{}", &field.label); quote! { let mut #ident = None; } @@ -123,7 +121,7 @@ fn unwrap_expected_fields(fields: &[parse::Field]) -> Vec Vec { @@ -161,7 +159,7 @@ fn all_fields(fields: &[parse::Field]) -> Vec { fields .iter() .map(|field| { - let ident = syn::Ident::new(&field.label, proc_macro2::Span::call_site()); + let ident = format_ident!("{}", &field.label); quote! { #ident } diff --git a/tests/basics.rs b/tests/basics.rs index 0e51224..b60b4b4 100644 --- a/tests/basics.rs +++ b/tests/basics.rs @@ -1,5 +1,39 @@ use serde_indexed::{DeserializeIndexed, SerializeIndexed}; +/// buffer should be big enough to hold serialized object. +fn cbor_serialize( + object: &T, + buffer: &mut [u8], +) -> Result { + let writer = serde_cbor::ser::SliceWrite::new(buffer); + let mut ser = serde_cbor::Serializer::new(writer); + + object.serialize(&mut ser)?; + + let writer = ser.into_inner(); + let size = writer.bytes_written(); + + Ok(size) +} + +/// may or may not modify buffer to hold temporary data. +/// buffer may be longer than serialized T. +fn cbor_deserialize<'de, T: serde::Deserialize<'de>>( + buffer: &'de mut [u8], +) -> Result { + let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(buffer); + serde::Deserialize::deserialize(&mut deserializer) +} + +/// scratch should be big enough to hold temporary data. +/// buffer must not have trailing data. +fn cbor_deserialize_with_scratch<'de, T: serde::Deserialize<'de>>( + buffer: &'de [u8], + scratch: &'de mut [u8], +) -> Result { + serde_cbor::de::from_slice_with_scratch(buffer, scratch) +} + mod some_keys { use super::*; @@ -7,7 +41,6 @@ mod some_keys { #[derive(Clone, Debug, PartialEq, SerializeIndexed, DeserializeIndexed)] #[serde_indexed(offset = 1)] - // #[serde_indexed(skip_nones)] pub struct SomeKeys { pub number: i32, pub bytes: [u8; 7], @@ -34,55 +67,24 @@ mod some_keys { } fn another_example() -> SomeKeys { - let mut string = heapless::String::new(); - string.push_str("so serde").unwrap(); - - let mut vector = heapless::Vec::::new(); - vector.push(42).unwrap(); - - SomeKeys { - number: -7, - bytes: [37u8; 7], - string, - option: Some(0xff), - vector, - } + let mut an_example = an_example(); + an_example.option = Some(0xff); + an_example } // in Python: cbor.dumps({1: -7, 2: [37]*7, 3: "so serde", 5: [42]*1}) const SERIALIZED_AN_EXAMPLE: &'static [u8] = b"\xa4\x01&\x02\x87\x18%\x18%\x18%\x18%\x18%\x18%\x18%\x03hso serde\x05\x81\x18*"; + // in Python: cbor.dumps({1: -7, 2: [37]*7, 3: "so serde", 4: 0xff, 5: [42]*1}) const SERIALIZED_ANOTHER_EXAMPLE: &'static [u8] = b"\xa5\x01&\x02\x87\x18%\x18%\x18%\x18%\x18%\x18%\x18%\x03hso serde\x04\x18\xff\x05\x81\x18*"; - fn cbor_serialize( - object: &T, - buffer: &mut [u8], - ) -> Result { - let writer = serde_cbor::ser::SliceWrite::new(buffer); - let mut ser = serde_cbor::Serializer::new(writer); - - object.serialize(&mut ser)?; - - let writer = ser.into_inner(); - let size = writer.bytes_written(); - - Ok(size) - } - - fn cbor_deserialize<'de, T: serde::Deserialize<'de>>( - buffer: &'de mut [u8], - ) -> Result { - let mut deserializer = serde_cbor::de::Deserializer::from_mut_slice(buffer); - serde::de::Deserialize::deserialize(&mut deserializer) - } - #[test] fn serialize() { let example = an_example(); - let mut buffer = [0u8; 1024]; + let mut buffer = [0u8; 64]; let size = cbor_serialize(&example, &mut buffer).unwrap(); assert_eq!(&buffer[..size], SERIALIZED_AN_EXAMPLE); @@ -90,10 +92,9 @@ mod some_keys { #[test] fn deserialize() { - let mut buffer = [0u8; 1024]; - buffer[..SERIALIZED_AN_EXAMPLE.len()].copy_from_slice(SERIALIZED_AN_EXAMPLE); - - let maybe_example: SomeKeys = cbor_deserialize(&mut buffer).unwrap(); + // no allocations need in this case. + let maybe_example: SomeKeys = + cbor_deserialize_with_scratch(SERIALIZED_AN_EXAMPLE, &mut []).unwrap(); assert_eq!(maybe_example, an_example()); } @@ -102,7 +103,7 @@ mod some_keys { fn another_serialize() { let example = another_example(); - let mut buffer = [0u8; 1024]; + let mut buffer = [0u8; 64]; let size = cbor_serialize(&example, &mut buffer).unwrap(); assert_eq!(&buffer[..size], SERIALIZED_ANOTHER_EXAMPLE); @@ -110,7 +111,9 @@ mod some_keys { #[test] fn another_deserialize() { - let mut buffer = [0u8; 1024]; + // could also use `cbor_deserialize_with_scratch` in this case, + // demonstrating the `cbor_deserialize` function. + let mut buffer = [0u8; SERIALIZED_ANOTHER_EXAMPLE.len()]; buffer[..SERIALIZED_ANOTHER_EXAMPLE.len()].copy_from_slice(SERIALIZED_ANOTHER_EXAMPLE); let maybe_example: SomeKeys = cbor_deserialize(&mut buffer).unwrap();