diff --git a/libshvproto-macros/Cargo.toml b/libshvproto-macros/Cargo.toml index a3b5450..893a803 100644 --- a/libshvproto-macros/Cargo.toml +++ b/libshvproto-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libshvproto-macros" -version = "0.1.0" +version = "0.1.1" edition = "2021" [lib] diff --git a/libshvproto-macros/src/lib.rs b/libshvproto-macros/src/lib.rs index cc9f3b3..199199b 100644 --- a/libshvproto-macros/src/lib.rs +++ b/libshvproto-macros/src/lib.rs @@ -13,20 +13,23 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream { match &input.data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { let mut struct_initializers = quote!{}; + let mut map_initializers = quote!{}; for field in fields { let identifier = field.ident.as_ref().unwrap(); let field_name = field .attrs.first() - .and_then(|x| x.meta.require_name_value().ok()) - .filter(|x| x.path.is_ident("field_name")) - .map(|x| if let syn::Expr::Lit(expr) = &x.value { expr } else { panic!("Expected a string literal for 'field_name'") }) - .map(|x| if let syn::Lit::Str(expr) = &x.lit { expr.value() } else { panic!("Expected a string literal for 'field_name'") }) + .and_then(|attr| attr.meta.require_name_value().ok()) + .filter(|meta_name_value| meta_name_value.path.is_ident("field_name")) + .map(|meta_name_value| if let syn::Expr::Lit(expr) = &meta_name_value.value { expr } else { panic!("Expected a string literal for 'field_name'") }) + .map(|literal| if let syn::Lit::Str(expr) = &literal.lit { expr.value() } else { panic!("Expected a string literal for 'field_name'") }) .unwrap_or_else(|| identifier.to_string().to_case(Case::Camel)); struct_initializers.extend(quote!{ #identifier: get_key(#field_name).and_then(|x| x.try_into())?, }); - + map_initializers.extend(quote!{ + (#field_name.into(), value.#identifier.into()), + }); } quote!{ impl TryFrom for #struct_identifier { @@ -71,13 +74,15 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream { impl TryFrom for #struct_identifier { type Error = String; fn try_from(value: shvproto::Map) -> Result { - let get_key = |key_name| value.get(key_name).ok_or_else(|| "Missing ".to_string() + key_name + " key"); - Ok(Self { - #struct_initializers - }) + Self::try_from(&value) } } + impl From<#struct_identifier> for shvproto::RpcValue { + fn from(value: #struct_identifier) -> Self { + [#map_initializers].into_iter().collect::().into() + } + } } } _ => panic!("This macro can only be used on a struct.") diff --git a/src/rpcvalue.rs b/src/rpcvalue.rs index 988915b..f8fe9c5 100644 --- a/src/rpcvalue.rs +++ b/src/rpcvalue.rs @@ -23,9 +23,9 @@ static EMPTY_METAMAP: OnceLock = OnceLock::new(); #[macro_export(local_inner_macros)] macro_rules! make_map { - ($( $key: expr => $val: expr ),*) => {{ + ($( $key: expr => $val: expr ),* $(,)?) => {{ let mut map = $crate::rpcvalue::Map::new(); - $( map.insert($key.to_string(), RpcValue::from($val)); )* + $( map.insert($key.to_string(), $crate::RpcValue::from($val)); )* map }} } diff --git a/tests/test.rs b/tests/test.rs index 6e6b73a..9fdfa6f 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -9,27 +9,15 @@ mod test { struct EmptyStruct { } - impl From for RpcValue { - fn from(_value: EmptyStruct) -> Self { - shvproto::make_map!().into() - } - } - #[derive(Debug,PartialEq,TryFromRpcValue)] struct OneFieldStruct { x: i32 } - impl From for RpcValue { - fn from(value: OneFieldStruct) -> Self { - shvproto::make_map!("x" => value.x).into() - } - } - #[derive(Debug,PartialEq,TryFromRpcValue)] struct TestStruct { int_field: i32, - #[field_name = "my_custom_field_name"] int_field_with_custom_field_name: i32, + #[field_name = "myCustomFieldName"] int_field_with_custom_field_name: i32, string_field: String, map_field: shvproto::Map, empty_struct_field: EmptyStruct, @@ -37,29 +25,14 @@ mod test { vec_int_field: Vec, vec_empty_struct_field: Vec, map_int_field: BTreeMap, - } - - impl From for RpcValue { - fn from(value: TestStruct) -> Self { - shvproto::make_map!( - "intField" => value.int_field, - "my_custom_field_name" => value.int_field_with_custom_field_name, - "stringField" => value.string_field, - "mapField" => value.map_field, - "emptyStructField" => value.empty_struct_field, - "oneFieldStruct" => value.one_field_struct, - "vecIntField" => value.vec_int_field, - "vecEmptyStructField" => value.vec_empty_struct_field, - "mapIntField" => value.map_int_field - ).into() - } + imap_field: BTreeMap, } #[test] fn derive_struct() { let x: RpcValue = shvproto::make_map!( "intField" => 123, - "my_custom_field_name" => 1234, + "myCustomFieldName" => 1234, "stringField" => "some_string", "mapField" => shvproto::make_map!( "some_key" => 123 @@ -68,22 +41,11 @@ mod test { "oneFieldStruct" => shvproto::make_map!("x" => 4565), "vecIntField" => vec![1_i32, 2_i32].into_iter().map(RpcValue::from).collect::>(), "vecEmptyStructField" => vec![shvproto::make_map!(), shvproto::make_map!()].into_iter().map(RpcValue::from).collect::>(), - "mapIntField" => [("aaa".to_string(), 111)].into_iter().collect::>() + "mapIntField" => [("aaa".to_string(), 111)].into_iter().collect::>(), + "imapField" => [(420, 111)].into_iter().collect::>(), ).into(); let y: TestStruct = x.clone().try_into().expect("Failed to parse"); - - assert_eq!(y, TestStruct { - int_field: 123, - string_field: "some_string".to_owned(), - int_field_with_custom_field_name: 1234, - map_field: shvproto::make_map!("some_key" => 123), - one_field_struct: OneFieldStruct {x: 4565}, - empty_struct_field: EmptyStruct{}, - vec_int_field: vec![1_i32, 2_i32], - vec_empty_struct_field: vec![EmptyStruct{}, EmptyStruct{}], - map_int_field: [("aaa".to_string(), 111)].into_iter().collect::>(), - }); assert_eq!(x, y.into()); } @@ -92,6 +54,6 @@ mod test { fn missing_fields() { let _x: TestStruct = shvproto::make_map!( "my_custom_field_name" => 1234 - ).try_into().expect("Asd"); + ).try_into().expect("Expected parse failure"); } }