From c67204fa742e7a153adefb77f54281ca34ec2403 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Fri, 19 Jun 2026 10:47:54 +0800 Subject: [PATCH 1/2] catalyst: add exclude_field_attributes --- README.md | 1 + complex-example/catalyst/src/lib.rs | 35 +++++++++++++++++++++++++++++ derive/src/catalyst.rs | 35 ++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 5 deletions(-) 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/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 { From 9ef019d3c8b086ec618f18f10b2195f1727817d0 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Fri, 19 Jun 2026 11:33:59 +0800 Subject: [PATCH 2/2] bump 0.12.5 --- Cargo.toml | 2 +- complex-example/Cargo.toml | 2 +- lib/Cargo.toml | 2 +- no-std-examples/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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/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/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"