Skip to content
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions complex-example/catalyst/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,26 @@ struct Amyloid {
#[complex(attribute(serde(default = "default_str")))]
pub extra_string: String,
pub extra_option: Option<usize>,
#[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")]
Expand Down Expand Up @@ -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
"#;
Expand Down
37 changes: 36 additions & 1 deletion complex-example/substrate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ pub struct Base {
pub field_bool: bool,
pub field_string: String,
pub field_option: Option<usize>,
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)]
Expand All @@ -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);
}
}
8 changes: 4 additions & 4 deletions derive/src/catalyst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -132,9 +132,9 @@ impl Catalyst {
#struct_name {
#(#catalyst_fields)*
},
#substrate_name {
#substrate_name::__substrate_new(
#(#substrate_fields)*
}
)
)
}
}
Expand Down
30 changes: 29 additions & 1 deletion derive/src/substrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,45 @@ 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
}
fn expose() {
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
Expand Down