Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exclude = [

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.4"
version = "0.12.5"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion complex-example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct-patch = { path = "../lib", features = ["catalyst"] }

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.4"
version = "0.12.5"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
35 changes: 35 additions & 0 deletions complex-example/catalyst/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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::<ExcludedAmyloidComplex>(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
Expand Down
35 changes: 30 additions & 5 deletions derive/src/catalyst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(crate) struct Catalyst {
bind: String,
keep_field_attribute: bool,
override_field_attributes: HashMap<String, Vec<TokenStream>>, // TODO handle no-std
exclude_field_attributes: Vec<String>,
}

struct Field {
Expand All @@ -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
Expand All @@ -48,6 +49,7 @@ impl Catalyst {
bind,
keep_field_attribute,
override_field_attributes,
exclude_field_attributes,
} = self;

let substrate_name: Ident = {
Expand Down Expand Up @@ -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::<Result<Vec<_>>>()?;

let unpack_complex_fields = raw_complex_fields
Expand Down Expand Up @@ -170,6 +172,7 @@ impl Catalyst {
let mut bind = String::new();
let mut keep_field_attribute = false;
let mut override_field_attributes = HashMap::<String, Vec<TokenStream>>::new();
let mut exclude_field_attributes = Vec::<String>::new();

for attr in attrs {
let attr_str = attr.path().to_string();
Expand Down Expand Up @@ -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::<Token![,]>()?;
}
}
}
_ => {
return Err(meta.error(format_args!(
"unknown catalyst/complex attribute `{}`",
Expand Down Expand Up @@ -261,6 +277,7 @@ impl Catalyst {
bind,
keep_field_attribute,
override_field_attributes,
exclude_field_attributes,
})
}
}
Expand All @@ -271,6 +288,7 @@ impl Field {
&self,
keep_field_attribute: bool,
override_field_attributes: &HashMap<String, Vec<TokenStream>>,
exclude_field_attributes: &[String],
) -> Result<TokenStream> {
let Field {
attrs,
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion no-std-examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "no-std-examples"
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.4"
version = "0.12.5"
edition = "2021"
license = "MIT"

Expand Down