From 30a044f3f21efe89cd248632b2285a1dbf89d30b Mon Sep 17 00:00:00 2001 From: dandyvica Date: Sun, 4 Aug 2024 16:51:00 +0200 Subject: [PATCH] move to strum --- Cargo.toml | 3 +- src/lib.rs | 34 +++++----- tests/integration_tests.rs | 28 ++++---- type2network_derive/src/enum/from.rs | 80 +++++++++++++++++++---- type2network_derive/src/lib.rs | 5 +- type2network_derive/src/struct_builder.rs | 21 +++--- 6 files changed, 115 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2669e9..8a4292f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,10 @@ either = "1.9.0" bytes = "1.5.0" [dev-dependencies] -enum_from = { git = "https://github.com/dandyvica/enum_from.git" } +num_enum = "0.7.3" serde = { version = "1.0.195", features = [ "derive" ] } + [[example]] name = "ntp" src = "examples/ntp.rs" diff --git a/src/lib.rs b/src/lib.rs index 9e04d07..348a1ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Traits and procedural macros to convert structures and enums into bigendian data streams. +//! Traits and procedural macros to convert structures and enums into `bigendian` data streams. //! //! It's used to send struct or enum data to the wire, or receive them from the wire. //! The trait is defined as: @@ -18,7 +18,7 @@ //! } //! ``` //! -//! It's using the [`byteorder`](https://docs.rs/byteorder/latest/byteorder) crate in order to convert integers or floats to a bigendian buffer of ```u8```. +//! It's extensively using the [`byteorder`](https://docs.rs/byteorder/latest/byteorder) crate in order to convert integers or floats to a bigendian buffer of ```u8```. //! It is compatible with other attributes like those provided by [`serde`](https://crates.io/crates/serde) crate. //! //! ## How to use it ? @@ -29,15 +29,17 @@ //! * ```#[derive(ToNetwork)]``` to auto-implement the ```FromNetworkOrder``` trait //! * ```#[derive(ToNetwork, FromNetwork)]``` to auto-implement the ```ToNetworkOrder``` & ```FromNetworkOrder``` traits //! -//! The ```FromNetworkOrder``` is only supported for C-like enums. For the ```ToNetworkOrder``` trait on C-like enums, it needs to be ```Copy, Clone```. +//! The ```ToNetworkOrder``` trait is supported for all structs or enums containing supported types (see below for a list of supported types). +//! +//! The ```FromNetworkOrder``` trait is only supported for C-like unit-only enums. For the ```ToNetworkOrder``` trait on C-like enums, it needs to be ```Copy, Clone```. //! -//! ## The ```#[deser]``` attribute +//! ## The ```#[from_network]``` field attribute //! In addition it's possible to add a field attribute on a struct's field for the ```FromNetworkOrder``` trait: //! -//! * ```#[deser(ignore)]``` : the field is not deserialized. -//! * ```#[deser(debug)]``` : a ```dbg!(self.field_name)``` statement is inserted after the field is being deserialized. -//! * ```#[deser(with_fn(func))]``` : the function ```func(&mut self) -> std::io::Result<()>``` is called for that field. -//! * ```#[deser(with_code(code))]``` : the ```code``` block is injected before the field is being deserialized. +//! * ```#[from_network(ignore)]``` : the field is not deserialized. +//! * ```#[from_network(debug)]``` : a ```dbg!(self.field_name)``` statement is inserted after the field is being deserialized. +//! * ```#[from_network(with_fn(func))]``` : the function ```func(&mut self) -> std::io::Result<()>``` is called for that field. +//! * ```#[from_network(with_code(block))]``` : the ```code``` block is injected before the field is being deserialized. //! //! # Examples //! @@ -49,12 +51,12 @@ //! y: u16, //! //! // last field is not deserialized -//! #[deser(ignore)] +//! #[from_network(ignore)] //! z: u16, //! } //! //! // this function will be called for z field -//! fn update(p: &mut PointFn) { +//! fn update(p: &mut PointFn) -> std::io::Result<()> { //! p.z = 3; //! } //! @@ -64,7 +66,7 @@ //! y: u16, //! //! // last field is not deserialized -//! #[deser(with_fn(update))] +//! #[from_network(with_fn(update))] //! z: u16, //! } //! @@ -75,7 +77,7 @@ //! y: u16, //! //! // last field is not deserialized -//! #[deser(with_code(self.z = 0xFFFF;))] +//! #[from_network(with_code(self.z = 0xFFFF;))] //! z: u16, //! } //! ``` @@ -107,12 +109,12 @@ //! //! ## Examples //! Two examples can be found in the ```examples``` directory: -//! +//! //! * ntp //! * dns -//! -//! In general, you should add the ```#[derive(ToNetwork, FromNetwork)]``` derives to benefit from the procedural macros which automatically convert structure to network order back and forth. They are included using: -//! +//! +//! In general, you should add the ```#[derive(ToNetwork, FromNetwork)]``` derive macros to benefit from the procedural macros which automatically convert structure to network order back and forth. They are included using: +//! //! ```rust //! use type2network::{FromNetworkOrder, ToNetworkOrder}; //! use type2network_derive::{FromNetwork, ToNetwork}; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index cce183a..bcef1ad 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,11 +1,12 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use num_enum::{FromPrimitive, TryFromPrimitive}; use serde::Serialize; // some tests for structs use type2network::{FromNetworkOrder, ToNetworkOrder}; use type2network_derive::{FromNetwork, ToNetwork}; -use enum_from::EnumTryFrom; +// use enum_from::EnumTryFrom; // used for boiler plate unit tests for integers, floats etc pub fn to_network_test(val: &T, size: usize, v: &[u8]) { @@ -108,6 +109,7 @@ fn struct_lifetime_to() { } #[test] +#[allow(dead_code)] fn struct_lifetime_from() { #[derive(Debug, PartialEq, FromNetwork)] struct DataLifeTimeWithTypeParam<'a, T, V> @@ -167,7 +169,8 @@ fn enum_c_like() { #[test] #[allow(dead_code)] fn enum_simple() { - #[derive(Debug, Default, PartialEq, Copy, Clone, ToNetwork, FromNetwork, EnumTryFrom)] + #[derive(Debug, Default, PartialEq, Copy, Clone, TryFromPrimitive, ToNetwork, FromNetwork)] + #[from_network(TryFrom)] #[repr(u64)] enum Color { #[default] @@ -185,7 +188,8 @@ fn enum_simple() { #[test] #[allow(dead_code)] fn enum_opcode() { - #[derive(Debug, Copy, Clone, PartialEq, EnumTryFrom, ToNetwork, FromNetwork)] + #[derive(Debug, Copy, Clone, PartialEq, ToNetwork, FromNetwork, FromPrimitive)] + #[from_network(From)] #[repr(u16)] pub enum OpCodeReserved { Query = 0, //[RFC1035] @@ -196,7 +200,7 @@ fn enum_opcode() { Update = 5, // [RFC2136] DOS = 6, // DNS Stateful Operations (DSO) [RFC8490] - #[fallback] + #[num_enum(catch_all)] Reserved(u16), } @@ -206,6 +210,8 @@ fn enum_opcode() { } } + + let op = OpCodeReserved::IQuery; to_network_test(&op, 2, &[0, 1]); let op = OpCodeReserved::Reserved(55); @@ -258,7 +264,7 @@ fn struct_attr_no() { y: u16, // last field is not deserialized - #[deser(ignore)] + #[from_network(ignore)] z: u16, } @@ -284,7 +290,7 @@ fn struct_attr_fn() { y: u16, // last field is not deserialized - #[deser(with_fn(update))] + #[from_network(with_fn(update))] z: u16, } @@ -304,7 +310,7 @@ fn struct_attr_code() { y: u16, // last field is not deserialized - #[deser(with_code(self.z = 0xFFFF;))] + #[from_network(with_code(self.z = 0xFFFF;))] z: u16, } @@ -320,10 +326,10 @@ fn struct_attr_code() { fn struct_debug() { #[derive(Debug, Default, PartialEq, ToNetwork, FromNetwork)] struct PointDebug { - #[deser(debug)] + #[from_network(debug)] x: u16, - #[deser(debug)] + #[from_network(debug)] y: u16, } } @@ -333,10 +339,10 @@ fn struct_serde() { #[derive(Debug, Default, PartialEq, ToNetwork, FromNetwork, Serialize)] struct PointDebug { #[serde(skip_serializing)] - #[deser(debug)] + #[from_network(debug)] x: u16, - #[deser(debug)] + #[from_network(debug)] y: u16, } } diff --git a/type2network_derive/src/enum/from.rs b/type2network_derive/src/enum/from.rs index 9068999..089b04b 100644 --- a/type2network_derive/src/enum/from.rs +++ b/type2network_derive/src/enum/from.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{DataEnum, DeriveInput}; +use syn::{Attribute, DataEnum, DeriveInput}; use syn_utils::*; @@ -10,24 +10,44 @@ impl EnumDeriveBuilder { pub fn from_network(ast: &DeriveInput, _de: &DataEnum) -> proc_macro2::TokenStream { let enum_name = &ast.ident; let enum_string = enum_name.to_string(); + + // which trait does the enum implement ? From or TryFrom or none of these ? + let implemented_trait = get_from_or_tryfrom(&ast.attrs); + let ty = SynUtils::repr_size(&ast.attrs) .unwrap_or_else(|| unimplemented!("repr size is mandatory on enum {}", enum_name)); let value_expr = build_value(&ty); - quote! { - impl<'a> FromNetworkOrder<'a> for #enum_name { - fn deserialize_from(&mut self, buffer: &mut std::io::Cursor<&'a [u8]>) -> std::io::Result<()> { - #value_expr - match <#enum_name>::try_from(value) { - Ok(ct) => { - *self = ct; - Ok(()) + // the implementation of FromNetworkOrder depends on whether From or TryFrom is implemented + match implemented_trait { + TryFromOrFrom::From => quote! { + impl<'a> FromNetworkOrder<'a> for #enum_name { + fn deserialize_from(&mut self, buffer: &mut std::io::Cursor<&'a [u8]>) -> std::io::Result<()> { + #value_expr + *self = <#enum_name>::from(value); + Ok(()) + } + } + }, + TryFromOrFrom::TryFrom => quote! { + impl<'a> FromNetworkOrder<'a> for #enum_name { + fn deserialize_from(&mut self, buffer: &mut std::io::Cursor<&'a [u8]>) -> std::io::Result<()> { + #value_expr + match <#enum_name>::try_from(value) { + Ok(ct) => { + *self = ct; + Ok(()) + } + _ => Err(std::io::Error::other(format!("error converting value '{}' to enum type {}", value, #enum_string))), } - _ => Err(std::io::Error::other(format!("error converting value '{}' to enum type {}", value, #enum_string))), } } - } + }, + TryFromOrFrom::None => panic!( + "at least, '{}' should implement From or TryFrom trait", + enum_string + ), } } } @@ -43,6 +63,44 @@ fn build_value(ty: &TokenStream) -> proc_macro2::TokenStream { } } +// FromNetwork for enums makes it mandatory to impl either From or TryFrom +// This is hinted using the #[from_network(From)] ou #[from_network(TryFrom)] outer attribute +enum TryFromOrFrom { + From, + TryFrom, + None, +} + +fn get_from_or_tryfrom(attrs: &[Attribute]) -> TryFromOrFrom { + let mut result = TryFromOrFrom::None; + + // loop through attributes + for attr in attrs { + // we found the #[tonetwork] attributes + if attr.path().is_ident("from_network") { + attr.parse_nested_meta(|meta| { + // #[from_network(From)] + if meta.path.is_ident("From") { + result = TryFromOrFrom::From; + return Ok(()); + } + + // #[from_network(TryFrom)] + if meta.path.is_ident("TryFrom") { + result = TryFromOrFrom::TryFrom; + return Ok(()); + } + + // neither From nor TryFrom was found + return Ok(()); + }) + .unwrap(); + } + } + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/type2network_derive/src/lib.rs b/type2network_derive/src/lib.rs index 554f3d0..7286be7 100644 --- a/type2network_derive/src/lib.rs +++ b/type2network_derive/src/lib.rs @@ -8,9 +8,6 @@ use struct_builder::{StructBuilder, StructDeriveBuilder}; mod r#enum; use r#enum::{EnumBuilder, EnumDeriveBuilder}; -// mod enum_builder; -// use enum_builder::{EnumBuilder, EnumDeriveBuilder}; - mod generics; #[proc_macro_derive(ToNetwork)] @@ -23,7 +20,7 @@ pub fn to_network(input: TokenStream) -> TokenStream { ) } -#[proc_macro_derive(FromNetwork, attributes(deser))] +#[proc_macro_derive(FromNetwork, attributes(from_network))] pub fn from_network(input: TokenStream) -> TokenStream { derive_helper( input, diff --git a/type2network_derive/src/struct_builder.rs b/type2network_derive/src/struct_builder.rs index 529a2fd..c579cff 100644 --- a/type2network_derive/src/struct_builder.rs +++ b/type2network_derive/src/struct_builder.rs @@ -117,18 +117,13 @@ fn process_named_field(field: &Field) -> proc_macro2::TokenStream { let field_name = field.ident.as_ref().unwrap(); //find the attribute containing #[deser] - let deser_attr = field + let from_attr = field .attrs .iter() - .find(|attr| attr.path().is_ident("deser")); - - // we only support 1 attribute - // if field.attrs.len() > 1 { - // unimplemented!("only support a single attribue on field {}", field_name); - // } + .find(|attr| attr.path().is_ident("from_network")); // analyze attribute - let kind = if let Some(deser) = deser_attr { + let kind = if let Some(deser) = from_attr { process_attr(&deser) } else { AttrKind::NoAttribute @@ -178,8 +173,8 @@ fn process_attr(attr: &Attribute) -> AttrKind { } // the only attribute we process is "deser" - if !attr.path().is_ident("deser") { - unimplemented!("only #[deser] is a valid attribute"); + if !attr.path().is_ident("from_network") { + unimplemented!("only #[from_network] is a valid attribute"); } let mut kind = AttrKind::default(); @@ -203,7 +198,7 @@ fn process_attr(attr: &Attribute) -> AttrKind { return Ok(()); } - unimplemented!("malformed deser(with_fn) attribute") + unimplemented!("malformed from_network(with_fn) attribute") } // #[deser(with_block({ let x = 9; }))] @@ -218,7 +213,7 @@ fn process_attr(attr: &Attribute) -> AttrKind { return Ok(()); } - unimplemented!("deser attribute meta not supported") + unimplemented!("#from_network attribute meta not supported") } // #[deser(debug)] @@ -227,7 +222,7 @@ fn process_attr(attr: &Attribute) -> AttrKind { return Ok(()); } - Err(meta.error("unrecognized deser attribute")) + Err(meta.error("unrecognized #from_network attribute")) }); kind