diff --git a/README.md b/README.md index 36a4b50..8aea66a 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,6 @@ assert_eq!(item.list, vec![7]); #### Case 3 - Extend a struct from a crate Deriving `Substrate` on a struct will help you expose the field information, and you can easy to expose in build.rs of other crate. Deriving `Catalyst` on can read the field information of Substrate and generate a new Complex struct. -All the fields in substrate and catalyst need be public, and the fields in complex are also public. The overall behavior likes [chemical catalysts](https://en.wikipedia.org/wiki/Enzyme_catalysis), a catalyst **bind** on a substrate to form a complex struct, which has all fields from substrate and catalyst. Also, a complex can **decouple** without clone and return a catalyst and substrate. Check the [complex-example](./complex-example/catalyst/src/lib.rs). In the future, we may have a sub feature with catalyst, such that it will provide no memory moving decouple, but need unsafe code. diff --git a/complex-example/catalyst/src/lib.rs b/complex-example/catalyst/src/lib.rs index 9d47923..577c0d8 100644 --- a/complex-example/catalyst/src/lib.rs +++ b/complex-example/catalyst/src/lib.rs @@ -14,12 +14,26 @@ struct Amyloid { #[complex(attribute(serde(default = "default_str")))] pub extra_string: String, pub extra_option: Option, + #[complex(attribute(serde(default = "default_extra_private_number")))] + extra_private_number: u8, } fn default_str() -> String { "default".to_string() } +fn default_extra_private_number() -> u8 { + 7 +} + +#[allow(dead_code)] +impl AmyloidComplex { + /// Sum up the private_number(from Substrate) and the extra_private_number(from Catalyst) + fn private_number_sum(&self) -> u8 { + self.private_number + self.extra_private_number + } +} + #[derive(Catalyst)] #[catalyst(bind = Base)] #[complex(name = "SmallCpx")] @@ -60,32 +74,42 @@ mod tests { let amyloid = Amyloid::default(); let complex = amyloid.bind(substrate); assert_eq!(complex.field_bool, true); + assert_eq!(complex.private_number_sum(), 0); let toml_str = toml::to_string_pretty(&complex).unwrap(); assert_eq!( toml_str, r#"field_bool = true field_string = "" +private_number = 0 extra_bool = false extra_string = "" +extra_private_number = 0 "# ); let toml_str = r#" field_bool = true field_string = "" +private_number = 1 extra_bool = true "#; let complex: AmyloidComplex = toml::from_str(toml_str).unwrap(); assert_eq!(complex.extra_string, "default"); + + // the `extra_private_number` is 7 generated by `default_extra_private_number`, when serialize with a missing field + // such that the `private_number` + `extra_private_number` = 1 + 7 = 8 + assert_eq!(complex.private_number_sum(), 8); } #[test] fn override_works() { let toml_str = r#"field_bool = false +private_number = 0 extra_bool = false "#; let complex: SmallCpx = toml::from_str(toml_str).unwrap(); assert_eq!(complex.field_string, "default"); let toml_str = r#"field_bool = false +private_number = 0 renamed_field = "Renamed" extra_bool = false "#; diff --git a/complex-example/substrate/src/lib.rs b/complex-example/substrate/src/lib.rs index 2833a84..5fae9ce 100644 --- a/complex-example/substrate/src/lib.rs +++ b/complex-example/substrate/src/lib.rs @@ -9,12 +9,17 @@ pub struct Base { pub field_bool: bool, pub field_string: String, pub field_option: Option, + private_number: u8, } impl Base { pub fn has_bool(&self) -> bool { self.field_bool } + + pub fn get_private_number(&self) -> u8 { + self.private_number + } } #[cfg(test)] @@ -25,9 +30,39 @@ mod tests { fn expose_works() { assert_eq!( Base::expose_content(), - r#"{"named":[{"attrs":[{"style":"outer","meta":{"list":{"path":{"segments":[{"ident":"serde"}]},"delimiter":"paren","tokens":[{"ident":"default"}]}}}],"vis":"pub","ident":"field_bool","colon_token":true,"ty":{"path":{"segments":[{"ident":"bool"}]}}},{"vis":"pub","ident":"field_string","colon_token":true,"ty":{"path":{"segments":[{"ident":"String"}]}}},{"vis":"pub","ident":"field_option","colon_token":true,"ty":{"path":{"segments":[{"ident":"Option","arguments":{"angle_bracketed":{"args":[{"type":{"path":{"segments":[{"ident":"usize"}]}}}]}}}]}}}]}"# + r#"{"named":[{"attrs":[{"style":"outer","meta":{"list":{"path":{"segments":[{"ident":"serde"}]},"delimiter":"paren","tokens":[{"ident":"default"}]}}}],"vis":"pub","ident":"field_bool","colon_token":true,"ty":{"path":{"segments":[{"ident":"bool"}]}}},{"vis":"pub","ident":"field_string","colon_token":true,"ty":{"path":{"segments":[{"ident":"String"}]}}},{"vis":"pub","ident":"field_option","colon_token":true,"ty":{"path":{"segments":[{"ident":"Option","arguments":{"angle_bracketed":{"args":[{"type":{"path":{"segments":[{"ident":"usize"}]}}}]}}}]}}},{"ident":"private_number","colon_token":true,"ty":{"path":{"segments":[{"ident":"u8"}]}}}]}"# ); let _fields: syn::Fields = syn_serde::json::from_str(&Base::expose_content()).unwrap(); } + + #[test] + fn substrate_new_works() { + let b = Base::__substrate_new( + true, + "test".to_string(), + Some(100), + 7u8, + ); + assert_eq!(b.field_bool, true); + assert_eq!(b.field_string, "test"); + assert_eq!(b.field_option, Some(100)); + assert_eq!(b.get_private_number(), 7); + } + + #[test] + fn substrate_unpack_works() { + let b = Base::__substrate_new( + true, + "test".to_string(), + Some(100), + 7u8, + ); + let (field_bool, field_string, field_option, private_number) = b.__substrate_unpack(); + + assert_eq!(field_bool, true); + assert_eq!(field_string, "test"); + assert_eq!(field_option, Some(100)); + assert_eq!(private_number, 7); + } } diff --git a/derive/src/catalyst.rs b/derive/src/catalyst.rs index d019b49..6ec7199 100644 --- a/derive/src/catalyst.rs +++ b/derive/src/catalyst.rs @@ -105,9 +105,9 @@ impl Catalyst { let catalyst_impl = quote! { impl struct_patch::traits::Catalyst < #substrate_name, #complex_struct_name > for #struct_name { fn bind(self, s: #substrate_name) -> #complex_struct_name { - let #substrate_name { + let ( #(#substrate_fields)* - } = s; + ) = s.__substrate_unpack(); let #struct_name { #(#catalyst_fields)* } = self; @@ -132,9 +132,9 @@ impl Catalyst { #struct_name { #(#catalyst_fields)* }, - #substrate_name { + #substrate_name::__substrate_new( #(#substrate_fields)* - } + ) ) } } diff --git a/derive/src/substrate.rs b/derive/src/substrate.rs index e482de1..c546f2d 100644 --- a/derive/src/substrate.rs +++ b/derive/src/substrate.rs @@ -16,10 +16,17 @@ impl Substrate { fields, } = self; + let mut field_idents = Vec::new(); + let mut field_tys = Vec::new(); + for field in fields.iter() { + field_idents.push(field.ident.clone()); + field_tys.push(field.ty.clone()); + } + let active_site = json::to_string(fields); Ok(quote! { - impl struct_patch::traits::Substrate for #struct_name { + impl struct_patch::traits::Substrate for #struct_name { fn expose_content() -> &'static str { #active_site } @@ -27,6 +34,27 @@ impl Substrate { println!("cargo:rustc-env={}={}", stringify!(#struct_name), Self::expose_content()); } } + impl #struct_name { + /// Support `decouple` of Complex with private fields, a new function generated by Substrate macro + pub fn __substrate_new( + #( #field_idents: #field_tys, )* + ) -> #struct_name { + #struct_name { + #( #field_idents, )* + } + } + + /// Support `bind` of Catalyst with private fields, an unpack function generated by Substrate macro + pub fn __substrate_unpack( + self + ) -> ( + #( #field_tys, )* + ) { + ( + #( self.#field_idents, )* + ) + } + } }) } /// Parse the substrate struct