use crate::validation::error_messages::ErrorMessages;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldAttributes {
pub required: bool,
pub setter_name: Option<String>,
pub setter_prefix: Option<String>,
pub default_value: Option<syn::Expr>,
pub skip_setter: bool,
pub impl_into: Option<bool>,
pub converter: Option<syn::Expr>,
pub builder_method: bool,
}
impl Default for FieldAttributes {
fn default() -> Self {
Self {
required: false,
setter_name: None,
setter_prefix: None,
default_value: None,
skip_setter: false,
impl_into: None,
converter: None,
builder_method: false,
}
}
}
impl FieldAttributes {
pub fn validate(&self) -> syn::Result<()> {
if self.setter_prefix.is_some() && self.skip_setter {
return Err(ErrorMessages::structured_error_span(
proc_macro2::Span::call_site(),
"Field-level setter_prefix is incompatible with skip_setter",
Some("#[builder(setter_prefix)] and #[builder(skip_setter)] are incompatible"),
Some("remove one of these attributes"),
));
}
if self.impl_into.is_some() && self.skip_setter {
return Err(ErrorMessages::structured_error_span(
proc_macro2::Span::call_site(),
"Field-level impl_into is incompatible with skip_setter",
Some("#[builder(impl_into)] and #[builder(skip_setter)] are incompatible"),
Some("remove one of these attributes"),
));
}
if self.converter.is_some() && self.skip_setter {
return Err(ErrorMessages::structured_error_span(
proc_macro2::Span::call_site(),
"Field-level converter is incompatible with skip_setter",
Some("#[builder(converter)] and #[builder(skip_setter)] are incompatible"),
Some("remove one of these attributes"),
));
}
if self.converter.is_some() && self.impl_into.is_some() {
return Err(ErrorMessages::structured_error_span(
proc_macro2::Span::call_site(),
"Field-level converter is incompatible with impl_into",
Some("#[builder(converter)] and #[builder(impl_into)] are incompatible"),
Some("use either custom converter or impl_into, not both"),
));
}
if self.builder_method && self.skip_setter {
return Err(ErrorMessages::structured_error_span(
proc_macro2::Span::call_site(),
"Field-level builder_method is incompatible with skip_setter",
Some("#[builder(builder_method)] and #[builder(skip_setter)] are incompatible"),
Some("remove one of these attributes"),
));
}
if let Some(setter_prefix) = &self.setter_prefix {
if setter_prefix.is_empty() {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
"Setter prefix cannot be empty",
));
}
if setter_prefix.chars().next().is_some_and(|c| c.is_numeric()) {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!(
"Invalid setter prefix '{setter_prefix}'. Setter prefixes cannot start with a number. \
Use a valid identifier prefix like 'with_' or 'set_'."
),
));
}
if !setter_prefix
.chars()
.all(|c| c.is_alphanumeric() || c == '_')
{
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!(
"Invalid setter prefix '{setter_prefix}'. Setter prefixes must contain only alphanumeric characters and underscores."
),
));
}
}
if let Some(setter_name) = &self.setter_name {
if setter_name.is_empty() {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
"Setter name cannot be empty",
));
}
if syn::parse_str::<syn::Ident>(setter_name).is_err() {
if !setter_name.starts_with("r#") {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!(
"Invalid setter name '{setter_name}'. Setter names must be valid Rust identifiers. \
Use raw identifier syntax (r#name) for keywords."
),
));
}
}
}
Ok(())
}
}
pub fn parse_field_attributes(attrs: &[syn::Attribute]) -> syn::Result<FieldAttributes> {
let mut field_attributes = FieldAttributes::default();
for attr in attrs {
if attr.path().is_ident("builder") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("required") {
if field_attributes.required {
return Err(meta.error("Duplicate required attribute. Only one required is allowed per field"));
}
field_attributes.required = true;
Ok(())
} else if meta.path.is_ident("skip_setter") {
if field_attributes.skip_setter {
return Err(meta.error("Duplicate skip_setter attribute. Only one skip_setter is allowed per field"));
}
field_attributes.skip_setter = true;
Ok(())
} else if meta.path.is_ident("setter_name") {
let value = meta.value()?;
let lit_str: syn::LitStr = value.parse()?;
let setter_name = lit_str.value();
if setter_name.is_empty() {
return Err(meta.error("Setter name cannot be empty"));
}
if field_attributes.setter_name.is_some() {
return Err(meta.error("Duplicate setter_name attribute. Only one setter_name is allowed per field"));
}
field_attributes.setter_name = Some(setter_name);
Ok(())
} else if meta.path.is_ident("setter_prefix") {
let value = meta.value()?;
let lit_str: syn::LitStr = value.parse()?;
let setter_prefix = lit_str.value();
if setter_prefix.is_empty() {
return Err(meta.error("Setter prefix cannot be empty"));
}
if field_attributes.setter_prefix.is_some() {
return Err(meta.error("Duplicate setter_prefix attribute. Only one setter_prefix is allowed per field"));
}
field_attributes.setter_prefix = Some(setter_prefix);
Ok(())
} else if meta.path.is_ident("default") {
let value = meta.value()?;
if field_attributes.default_value.is_some() {
return Err(meta.error("Duplicate default attribute. Only one default is allowed per field"));
}
let expr: syn::Expr = value.parse()?;
field_attributes.default_value = Some(expr);
Ok(())
} else if meta.path.is_ident("impl_into") {
if field_attributes.impl_into.is_some() {
return Err(meta.error("Duplicate impl_into attribute. Only one impl_into is allowed per field"));
}
if meta.input.peek(syn::Token![=]) {
let value = meta.value()?;
let lit_bool: syn::LitBool = value.parse()?;
field_attributes.impl_into = Some(lit_bool.value);
} else {
field_attributes.impl_into = Some(true);
}
Ok(())
} else if meta.path.is_ident("converter") {
let value = meta.value()?;
let expr: syn::Expr = value.parse()?;
if field_attributes.converter.is_some() {
return Err(meta.error("Duplicate converter attribute. Only one converter is allowed per field"));
}
field_attributes.converter = Some(expr);
Ok(())
} else if meta.path.is_ident("builder_method") {
if field_attributes.builder_method {
return Err(meta.error("Duplicate builder_method attribute. Only one builder_method is allowed per field"));
}
field_attributes.builder_method = true;
Ok(())
} else {
Err(meta.error(
"Unknown builder attribute. Supported attributes: required, setter_name, setter_prefix, default, skip_setter, impl_into, converter, builder_method"
))
}
})?;
}
}
field_attributes.validate()?;
Ok(field_attributes)
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_default_field_attributes() {
let attrs = FieldAttributes::default();
assert!(!attrs.required);
assert!(attrs.setter_name.is_none());
assert!(attrs.setter_prefix.is_none());
assert!(attrs.default_value.is_none());
assert!(!attrs.skip_setter);
assert!(attrs.impl_into.is_none());
}
#[test]
fn test_parse_required_attribute() {
let attrs = vec![parse_quote!(#[builder(required)])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_setter_name_attribute() {
let attrs = vec![parse_quote!(#[builder(setter_name = "set_field")])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("set_field".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
use proptest::prelude::*;
fn expr_strategy() -> impl Strategy<Value = &'static str> {
prop_oneof![
Just("42"),
Just("0"),
Just("100_000"),
Just("3.14"),
Just("0.0"),
Just("true"),
Just("false"),
Just("\"hello\""),
Just("\"\""),
Just("'a'"),
Just("Vec::new()"),
Just("String::new()"),
Just("Default::default()"),
Just("std::collections::HashMap::new()"),
Just("Vec::with_capacity(10)"),
Just("String::from(\"test\")"),
Just("None"),
Just("Some(42)"),
Just("1 + 2"),
Just("10 * 5"),
Just("[1, 2, 3]"),
Just("(1, 2)"),
Just("|| 42"),
Just("|x| x + 1"),
]
}
fn parse_attr_from_struct(attr_str: &str) -> syn::Attribute {
let struct_def = format!("struct Test {{ {} field: i32 }}", attr_str);
let input: syn::DeriveInput =
syn::parse_str(&struct_def).expect("struct definition should parse");
match input.data {
syn::Data::Struct(data) => match data.fields {
syn::Fields::Named(fields) => fields
.named
.into_iter()
.next()
.expect("struct should have a field")
.attrs
.into_iter()
.next()
.expect("field should have an attribute"),
_ => panic!("expected named fields"),
},
_ => panic!("expected struct"),
}
}
proptest! {
#[test]
fn prop_default_expression_parsing(expr_str in expr_strategy()) {
let expected_expr: syn::Expr = syn::parse_str(expr_str)
.expect("test expression should be valid");
let attr = parse_attr_from_struct(
&format!("#[builder(default = {})]", expr_str)
);
let result = parse_field_attributes(&[attr]);
prop_assert!(result.is_ok(), "Expression syntax should parse successfully for: {}", expr_str);
let attrs = result.unwrap();
prop_assert!(attrs.default_value.is_some(), "Should have default value");
let parsed_expr = attrs.default_value.unwrap();
let parsed_tokens = quote::quote!(#parsed_expr).to_string();
let expected_tokens = quote::quote!(#expected_expr).to_string();
prop_assert_eq!(
parsed_tokens,
expected_tokens,
"Expression should be preserved exactly"
);
}
}
#[test]
fn test_parse_skip_setter_attribute() {
let attrs = vec![parse_quote!(#[builder(skip_setter)])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.default_value.is_none());
assert!(field_attrs.skip_setter);
}
#[test]
fn test_parse_combined_attributes() {
let attrs = vec![parse_quote!(#[builder(required, setter_name = "set_name")])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("set_name".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_multiple_separate_attributes() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(setter_name = "custom_name")]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom_name".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_no_builder_attributes() {
let attrs = vec![parse_quote!(#[derive(Debug)])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_empty_setter_name_error() {
let attrs = vec![parse_quote!(#[builder(setter_name = "")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cannot be empty"));
}
#[test]
fn test_parse_empty_string_default_value() {
let attrs = vec![parse_quote!(#[builder(default = "")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let field_attrs = result.unwrap();
assert!(field_attrs.default_value.is_some());
}
#[test]
fn test_parse_unknown_attribute_error() {
let attrs = vec![parse_quote!(#[builder(unknown_attr)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unknown builder attribute"));
}
#[test]
fn test_parse_setter_prefix_attribute() {
let attrs = vec![parse_quote!(#[builder(setter_prefix = "with_")])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert_eq!(field_attrs.setter_prefix, Some("with_".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_combined_setter_prefix_attributes() {
let attrs = vec![parse_quote!(#[builder(required, setter_prefix = "set_")])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert_eq!(field_attrs.setter_prefix, Some("set_".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_setter_prefix_with_setter_name() {
let attrs = vec![parse_quote!(#[builder(setter_name = "custom", setter_prefix = "with_")])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom".to_string()));
assert_eq!(field_attrs.setter_prefix, Some("with_".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_empty_setter_prefix_error() {
let attrs = vec![parse_quote!(#[builder(setter_prefix = "")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Setter prefix cannot be empty"));
}
#[test]
fn test_validate_setter_prefix_with_skip_setter_error() {
let attrs = vec![parse_quote!(#[builder(setter_prefix = "with_", skip_setter)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("setter_prefix is incompatible with skip_setter"));
}
#[test]
fn test_validate_invalid_setter_prefix_starting_with_number() {
let attrs = vec![parse_quote!(#[builder(setter_prefix = "1invalid_")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("cannot start with a number"));
}
#[test]
fn test_validate_invalid_setter_prefix_special_chars() {
let attrs = vec![parse_quote!(#[builder(setter_prefix = "with-")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("alphanumeric characters and underscores"));
}
#[test]
fn test_validate_valid_setter_prefixes() {
let valid_prefixes = [
"with_", "set_", "use_", "add_", "remove_", "update_", "get_",
];
for prefix in valid_prefixes {
let attrs = vec![parse_quote!(#[builder(setter_prefix = #prefix)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok(), "Prefix '{prefix}' should be valid");
}
}
#[test]
fn test_parse_multiple_setter_prefix_attributes() {
let attrs = vec![
parse_quote!(#[builder(setter_prefix = "with_")]),
parse_quote!(#[builder(required)]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert_eq!(field_attrs.setter_prefix, Some("with_".to_string()));
assert!(field_attrs.required);
}
#[test]
fn test_field_attributes_validate_method() {
let valid_attrs = FieldAttributes {
required: true,
setter_name: Some("custom_name".to_string()),
setter_prefix: Some("with_".to_string()),
default_value: None,
skip_setter: false,
impl_into: None,
converter: None,
builder_method: false,
};
assert!(valid_attrs.validate().is_ok());
let invalid_attrs = FieldAttributes {
required: false,
setter_name: None,
setter_prefix: Some("with_".to_string()),
default_value: Some(syn::parse_str("42").unwrap()),
skip_setter: true,
impl_into: None,
converter: None,
builder_method: false,
};
let result = invalid_attrs.validate();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("incompatible with skip_setter"));
}
#[test]
fn test_field_attributes_default_includes_setter_prefix() {
let default_attrs = FieldAttributes::default();
assert!(!default_attrs.required);
assert!(default_attrs.setter_name.is_none());
assert!(default_attrs.setter_prefix.is_none());
assert!(default_attrs.default_value.is_none());
assert!(!default_attrs.skip_setter);
}
#[test]
fn test_parse_duplicate_setter_name_error() {
let attrs = vec![
parse_quote!(#[builder(setter_name = "first_name")]),
parse_quote!(#[builder(setter_name = "full_name")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate setter_name attribute"));
}
#[test]
fn test_parse_duplicate_required_error() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(required)]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate required attribute"));
}
#[test]
fn test_parse_duplicate_skip_setter_error() {
let attrs = vec![
parse_quote!(#[builder(skip_setter)]),
parse_quote!(#[builder(skip_setter)]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate skip_setter attribute"));
}
#[test]
fn test_parse_duplicate_default_error() {
let attrs = vec![
parse_quote!(#[builder(default = "first")]),
parse_quote!(#[builder(default = "second")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate default attribute"));
}
#[test]
fn test_parse_duplicate_setter_prefix_error() {
let attrs = vec![
parse_quote!(#[builder(setter_prefix = "with_")]),
parse_quote!(#[builder(setter_prefix = "set_")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate setter_prefix attribute"));
}
#[test]
fn test_parse_mixed_attributes_in_single_builder_attribute() {
let attrs =
vec![parse_quote!(#[builder(required, setter_name = "custom", default = "42")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let field_attrs = result.unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom".to_string()));
assert!(field_attrs.default_value.is_some());
}
#[test]
fn test_parse_mixed_attributes_across_multiple_builder_attributes() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(setter_name = "custom")]),
parse_quote!(#[builder(default = "42")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let field_attrs = result.unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom".to_string()));
assert!(field_attrs.default_value.is_some());
}
#[test]
fn test_parse_skip_setter_with_setter_name_only() {
let attrs = vec![
parse_quote!(#[builder(skip_setter)]),
parse_quote!(#[builder(setter_name = "custom")]),
parse_quote!(#[builder(default = "42")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let field_attrs = result.unwrap();
assert!(field_attrs.skip_setter);
assert_eq!(field_attrs.setter_name, Some("custom".to_string()));
assert!(field_attrs.default_value.is_some());
assert!(field_attrs.setter_prefix.is_none());
}
#[test]
fn test_parse_complex_valid_combination() {
let attrs = vec![
parse_quote!(#[builder(setter_name = "custom_name")]),
parse_quote!(#[builder(setter_prefix = "with_")]),
parse_quote!(#[builder(default = "42")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let field_attrs = result.unwrap();
assert!(!field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom_name".to_string()));
assert_eq!(field_attrs.setter_prefix, Some("with_".to_string()));
assert!(field_attrs.default_value.is_some());
assert!(!field_attrs.skip_setter);
}
#[test]
fn test_parse_duplicate_mixed_with_valid_attributes() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(setter_name = "first")]),
parse_quote!(#[builder(setter_name = "second")]), ];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate setter_name attribute"));
}
#[test]
fn test_parse_impl_into_flag_attribute() {
let attrs = vec![parse_quote!(#[builder(impl_into)])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
assert_eq!(field_attrs.impl_into, Some(true));
}
#[test]
fn test_parse_impl_into_true_attribute() {
let attrs = vec![parse_quote!(#[builder(impl_into = true)])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
assert_eq!(field_attrs.impl_into, Some(true));
}
#[test]
fn test_parse_impl_into_false_attribute() {
let attrs = vec![parse_quote!(#[builder(impl_into = false)])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
assert_eq!(field_attrs.impl_into, Some(false));
}
#[test]
fn test_parse_combined_attributes_with_impl_into() {
let attrs = vec![parse_quote!(#[builder(required, impl_into, setter_name = "set_field")])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("set_field".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
assert_eq!(field_attrs.impl_into, Some(true));
}
#[test]
fn test_parse_multiple_separate_attributes_with_impl_into() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(impl_into = false)]),
parse_quote!(#[builder(setter_name = "custom_name")]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom_name".to_string()));
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
assert_eq!(field_attrs.impl_into, Some(false));
}
#[test]
fn test_parse_duplicate_impl_into_error() {
let attrs = vec![
parse_quote!(#[builder(impl_into)]),
parse_quote!(#[builder(impl_into = false)]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate impl_into attribute"));
}
#[test]
fn test_validate_impl_into_with_skip_setter_error() {
let attrs = vec![parse_quote!(#[builder(impl_into, skip_setter)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("impl_into is incompatible with skip_setter"));
}
#[test]
fn test_validate_impl_into_false_with_skip_setter_error() {
let attrs = vec![parse_quote!(#[builder(impl_into = false, skip_setter)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("impl_into is incompatible with skip_setter"));
}
#[test]
fn test_validate_valid_impl_into_combinations() {
let attrs = vec![parse_quote!(#[builder(required, impl_into)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![parse_quote!(#[builder(impl_into, setter_name = "custom")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![parse_quote!(#[builder(impl_into, setter_prefix = "with_")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![parse_quote!(#[builder(impl_into, default = "42")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![parse_quote!(#[builder(impl_into = false)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
}
#[test]
fn test_field_attributes_default_includes_impl_into() {
let default_attrs = FieldAttributes::default();
assert!(!default_attrs.required);
assert!(default_attrs.setter_name.is_none());
assert!(default_attrs.setter_prefix.is_none());
assert!(default_attrs.default_value.is_none());
assert!(!default_attrs.skip_setter);
assert!(default_attrs.impl_into.is_none());
assert!(default_attrs.converter.is_none());
}
#[test]
fn test_parse_converter_simple() {
let attrs =
vec![parse_quote!(#[builder(converter = |value: String| value.to_uppercase())])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(!field_attrs.required);
assert!(field_attrs.setter_name.is_none());
assert!(field_attrs.setter_prefix.is_none());
assert!(field_attrs.default_value.is_none());
assert!(!field_attrs.skip_setter);
assert!(field_attrs.impl_into.is_none());
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_converter_basic_closure() {
let attrs =
vec![parse_quote!(#[builder(converter = |value: String| value.to_uppercase())])];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_converter_complex_closure() {
let attrs = vec![
parse_quote!(#[builder(converter = |values: Vec<String>| values.into_iter().collect())]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_converter_with_other_attributes() {
let attrs = vec![
parse_quote!(#[builder(required, converter = |value: String| value, setter_name = "custom")]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom".to_string()));
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_converter_with_setter_prefix() {
let attrs = vec![
parse_quote!(#[builder(converter = |value: String| value, setter_prefix = "with_")]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert_eq!(field_attrs.setter_prefix, Some("with_".to_string()));
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_converter_with_default() {
let attrs = vec![
parse_quote!(#[builder(converter = |value: String| value, default = "Vec::new()")]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.default_value.is_some());
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_converter_across_multiple_attributes() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(converter = |value: String| value)]),
parse_quote!(#[builder(setter_name = "custom")]),
];
let field_attrs = parse_field_attributes(&attrs).unwrap();
assert!(field_attrs.required);
assert_eq!(field_attrs.setter_name, Some("custom".to_string()));
assert!(field_attrs.converter.is_some());
}
#[test]
fn test_parse_duplicate_converter_same_attribute() {
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, converter = |y: i32| y)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate converter attribute"));
}
#[test]
fn test_parse_duplicate_converter_different_attributes() {
let attrs = vec![
parse_quote!(#[builder(converter = |x: i32| x)]),
parse_quote!(#[builder(converter = |y: i32| y)]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate converter attribute"));
}
#[test]
fn test_parse_duplicate_converter_mixed_with_valid() {
let attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(converter = |x: i32| x)]),
parse_quote!(#[builder(converter = |y: i32| y)]), parse_quote!(#[builder(setter_name = "custom")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Duplicate converter attribute"));
}
#[test]
fn test_validate_converter_with_skip_setter_error() {
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, skip_setter)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with skip_setter"));
}
#[test]
fn test_validate_converter_with_impl_into_flag_error() {
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, impl_into)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with impl_into"));
}
#[test]
fn test_validate_converter_with_impl_into_true_error() {
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, impl_into = true)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with impl_into"));
}
#[test]
fn test_validate_converter_with_impl_into_false_error() {
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, impl_into = false)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with impl_into"));
}
#[test]
fn test_validate_converter_with_skip_setter_across_attributes() {
let attrs = vec![
parse_quote!(#[builder(converter = |x: i32| x)]),
parse_quote!(#[builder(skip_setter)]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with skip_setter"));
}
#[test]
fn test_validate_converter_with_impl_into_across_attributes() {
let attrs = vec![
parse_quote!(#[builder(converter = |x: i32| x)]),
parse_quote!(#[builder(impl_into)]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with impl_into"));
}
#[test]
fn test_validate_converter_with_valid_combinations() {
let attrs = vec![parse_quote!(#[builder(required, converter = |x: i32| x)])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, setter_name = "custom")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![parse_quote!(#[builder(converter = |x: i32| x, setter_prefix = "with_")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs =
vec![parse_quote!(#[builder(converter = |x: Vec<i32>| x, default = "Vec::new()")])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![
parse_quote!(#[builder(required, converter = |x: i32| x, setter_name = "custom", setter_prefix = "with_", default = "42")]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok()); }
#[test]
fn test_parse_converter_complex_closures() {
let attrs = vec![
parse_quote!(#[builder(converter = |values: Vec<String>| values.into_iter().map(|s| s.to_uppercase()).collect())]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let field_attrs = result.unwrap();
assert!(field_attrs.converter.is_some());
let attrs =
vec![parse_quote!(#[builder(converter = |input: String| input.trim().to_string())])];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
let attrs = vec![
parse_quote!(#[builder(converter = |data: Vec<i32>| data.into_iter().filter(|&x| x > 0).sum::<i32>())]),
];
let result = parse_field_attributes(&attrs);
assert!(result.is_ok());
}
#[test]
fn test_field_attributes_validate_setter_function_combinations() {
let valid_attrs = FieldAttributes {
required: false,
setter_name: None,
setter_prefix: None,
default_value: None,
skip_setter: false,
impl_into: None,
converter: Some(syn::parse_str("|value: String| value").unwrap()),
builder_method: false,
};
assert!(valid_attrs.validate().is_ok());
let valid_attrs = FieldAttributes {
required: true,
setter_name: Some("custom".to_string()),
setter_prefix: Some("with_".to_string()),
default_value: Some(syn::parse_str("Vec::new()").unwrap()),
skip_setter: false,
impl_into: None,
converter: Some(syn::parse_str("|value: String| value").unwrap()),
builder_method: false,
};
assert!(valid_attrs.validate().is_ok());
let invalid_attrs = FieldAttributes {
required: false,
setter_name: None,
setter_prefix: None,
default_value: None,
skip_setter: true,
impl_into: None,
converter: Some(syn::parse_str("|value: String| value").unwrap()),
builder_method: false,
};
let result = invalid_attrs.validate();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with skip_setter"));
let invalid_attrs = FieldAttributes {
required: false,
setter_name: None,
setter_prefix: None,
default_value: None,
skip_setter: false,
impl_into: Some(true),
converter: Some(syn::parse_str("|value: String| value").unwrap()),
builder_method: false,
};
let result = invalid_attrs.validate();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Field-level converter is incompatible with impl_into"));
}
#[test]
fn test_field_attributes_default_includes_setter_function() {
let default_attrs = FieldAttributes::default();
assert!(!default_attrs.required);
assert!(default_attrs.setter_name.is_none());
assert!(default_attrs.setter_prefix.is_none());
assert!(default_attrs.default_value.is_none());
assert!(!default_attrs.skip_setter);
assert!(default_attrs.impl_into.is_none());
assert!(default_attrs.converter.is_none());
}
}