diff --git a/Cargo.toml b/Cargo.toml index 5a3f097..10895eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ exclude = [ [workspace.package] authors = ["Antonio Yang "] -version = "0.12.4" +version = "0.12.5" edition = "2021" categories = ["development-tools"] keywords = ["struct", "patch", "macro", "derive", "overlay"] diff --git a/README.md b/README.md index 8aea66a..97e0ff5 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ Struct attributes: - `#[patch(attribute(derive(...)))]`: add derives to the generated patch struct. - `#[catalyst(bind = "...")]`: decide the base structure. (catalyst feature) - `#[catalyst(keep_field_attribute)]`: All field attributes from a substrate or catalyst will be passed through to the complex, unless an override is explicitly specified for that field. (catalyst feature) +- `#[catalyst(exclude_field_attributes = ["..."])]`: When `keep_field_attribute` is used, specifies attribute names to exclude from being passed through to the complex struct fields. For example, `exclude_field_attributes = ["serde"]` strips all `#[serde(...)]` field attributes from the substrate before they reach the complex. (catalyst feature) - `#[complex(override_field_attribute("$substrate_field_name", ...))]`: override the complex field attribute, for example `serde(default = "default_str").` (catalyst feature) - `#[complex(name = "...")]`: change the name of the generated complex struct. (catalyst feature) - `#[complex(attribute(...))]`: add attributes to the generated complex struct. (catalyst feature) diff --git a/complex-example/Cargo.toml b/complex-example/Cargo.toml index 4ba5e4b..52070d7 100644 --- a/complex-example/Cargo.toml +++ b/complex-example/Cargo.toml @@ -9,7 +9,7 @@ struct-patch = { path = "../lib", features = ["catalyst"] } [workspace.package] authors = ["Antonio Yang "] -version = "0.12.4" +version = "0.12.5" edition = "2021" categories = ["development-tools"] keywords = ["struct", "patch", "macro", "derive", "overlay"] diff --git a/complex-example/catalyst/src/lib.rs b/complex-example/catalyst/src/lib.rs index 577c0d8..023d57d 100644 --- a/complex-example/catalyst/src/lib.rs +++ b/complex-example/catalyst/src/lib.rs @@ -34,6 +34,16 @@ impl AmyloidComplex { } } +#[derive(Default, Catalyst)] +#[catalyst(bind = Base)] +#[catalyst(keep_field_attribute)] +#[catalyst(exclude_field_attributes = ["serde"])] +#[complex(attribute(derive(Debug, Deserialize)))] +#[allow(dead_code)] +struct ExcludedAmyloid { + pub extra_bool: bool, +} + #[derive(Catalyst)] #[catalyst(bind = Base)] #[complex(name = "SmallCpx")] @@ -100,6 +110,31 @@ extra_bool = true assert_eq!(complex.private_number_sum(), 8); } + #[test] + fn exclude_field_attributes_works() { + // Base has `#[serde(default)]` on `field_bool`. With exclude_field_attributes = ["serde"], + // that attribute is stripped from the complex, making `field_bool` a required field. + let toml_missing_field_bool = r#"field_string = "test" +private_number = 0 +extra_bool = false +"#; + assert!( + toml::from_str::(toml_missing_field_bool).is_err(), + "field_bool should be required when serde attributes are excluded" + ); + + let toml_all_fields = r#"field_bool = true +field_string = "test" +private_number = 0 +extra_bool = false +"#; + let complex: ExcludedAmyloidComplex = + toml::from_str(toml_all_fields).expect("all required fields provided"); + assert_eq!(complex.field_bool, true); + assert_eq!(complex.field_string, "test"); + assert_eq!(complex.extra_bool, false); + } + #[test] fn override_works() { let toml_str = r#"field_bool = false diff --git a/derive/src/catalyst.rs b/derive/src/catalyst.rs index 6d65d93..43229e8 100644 --- a/derive/src/catalyst.rs +++ b/derive/src/catalyst.rs @@ -17,6 +17,7 @@ pub(crate) struct Catalyst { bind: String, keep_field_attribute: bool, override_field_attributes: HashMap>, // TODO handle no-std + exclude_field_attributes: Vec, } struct Field { @@ -32,8 +33,8 @@ const BIND: &str = "bind"; const NAME: &str = "name"; const ATTRIBUTE: &str = "attribute"; const OVERRIDE: &str = "override_field_attribute"; -// TODO #[catalyst(keep_field_attrs = [ "serde" ])] for more detail selections const KEEP_FIELD_ATTRIBUTE: &str = "keep_field_attribute"; +const EXCLUDE_FIELD_ATTRIBUTES: &str = "exclude_field_attributes"; impl Catalyst { /// let catalyst bind the substrate and generate the token stream for complex @@ -48,6 +49,7 @@ impl Catalyst { bind, keep_field_attribute, override_field_attributes, + exclude_field_attributes, } = self; let substrate_name: Ident = { @@ -76,7 +78,7 @@ impl Catalyst { let complex_fields = raw_complex_fields .iter() - .map(|f| f.to_token_stream(*keep_field_attribute, override_field_attributes)) + .map(|f| f.to_token_stream(*keep_field_attribute, override_field_attributes, exclude_field_attributes)) .collect::>>()?; let unpack_complex_fields = raw_complex_fields @@ -170,6 +172,7 @@ impl Catalyst { let mut bind = String::new(); let mut keep_field_attribute = false; let mut override_field_attributes = HashMap::>::new(); + let mut exclude_field_attributes = Vec::::new(); for attr in attrs { let attr_str = attr.path().to_string(); @@ -225,9 +228,22 @@ impl Catalyst { } } KEEP_FIELD_ATTRIBUTE if attr_str == CATALYST => { - // #[catalyst(KEEP_FIELD_ATTRIBUTE )] + // #[catalyst(keep_field_attribute)] keep_field_attribute = true; } + EXCLUDE_FIELD_ATTRIBUTES if attr_str == CATALYST => { + // #[catalyst(exclude_field_attributes = ["schemars", "other"])] + let value = meta.value()?; + let content; + syn::bracketed!(content in value); + while !content.is_empty() { + let lit: LitStr = content.parse()?; + exclude_field_attributes.push(lit.value()); + if content.peek(Token![,]) { + content.parse::()?; + } + } + } _ => { return Err(meta.error(format_args!( "unknown catalyst/complex attribute `{}`", @@ -261,6 +277,7 @@ impl Catalyst { bind, keep_field_attribute, override_field_attributes, + exclude_field_attributes, }) } } @@ -271,6 +288,7 @@ impl Field { &self, keep_field_attribute: bool, override_field_attributes: &HashMap>, + exclude_field_attributes: &[String], ) -> Result { let Field { attrs, @@ -291,9 +309,16 @@ impl Field { if keep_field_attribute { if attrs.len() > 0 { - // from substrate + // from substrate: filter out excluded attribute namespaces + let filtered_attrs: Vec<&Attribute> = attrs + .iter() + .filter(|a| { + let path = a.path().to_token_stream().to_string(); + !exclude_field_attributes.iter().any(|e| path == *e) + }) + .collect(); Ok(quote! { - #( #attrs )* + #( #filtered_attrs )* pub #ident: #ty, }) } else { diff --git a/lib/Cargo.toml b/lib/Cargo.toml index cd696f9..bf9f144 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -12,7 +12,7 @@ readme.workspace = true rust-version.workspace = true [dependencies] -struct-patch-derive = { version = "=0.12.4", path = "../derive" } +struct-patch-derive = { version = "=0.12.5", path = "../derive" } [dev-dependencies] serde_json = "1.0" diff --git a/no-std-examples/Cargo.toml b/no-std-examples/Cargo.toml index 6e8b683..f3a29e4 100644 --- a/no-std-examples/Cargo.toml +++ b/no-std-examples/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "no-std-examples" authors = ["Antonio Yang "] -version = "0.12.4" +version = "0.12.5" edition = "2021" license = "MIT"