use crate::attributes::{parse_field_attributes, FieldAttributes};
use crate::utils::field_utils::{
resolve_effective_impl_into, resolve_setter_parameter_config, DefaultConfig, SetterConfig,
};
use crate::utils::identifiers::strip_raw_identifier_prefix;
use crate::validation::error_messages::ErrorMessages;
use quote::quote;
use std::borrow::Cow;
use syn::{Ident, Type};
#[derive(Debug, Clone)]
pub struct FieldInfo {
name: Ident,
ty: Type,
attributes: FieldAttributes,
}
impl FieldInfo {
pub fn from_syn_field(name: Ident, ty: Type, attrs: &[syn::Attribute]) -> syn::Result<Self> {
let attributes = parse_field_attributes(attrs)?;
let field_info = Self {
name,
ty,
attributes,
};
field_info.validate_configuration()?;
Ok(field_info)
}
pub fn name(&self) -> &Ident {
&self.name
}
pub fn field_type(&self) -> &Type {
&self.ty
}
pub fn attributes(&self) -> &FieldAttributes {
&self.attributes
}
pub fn is_required(&self) -> bool {
self.attributes.required
}
#[cfg(test)]
pub fn new_for_test(
name: syn::Ident,
ty: syn::Type,
attributes: crate::attributes::FieldAttributes,
) -> Self {
Self {
name,
ty,
attributes,
}
}
pub fn is_optional(&self) -> bool {
!self.is_required()
}
pub fn should_generate_setter(&self) -> bool {
!self.attributes.skip_setter
}
pub fn has_custom_default(&self) -> bool {
self.attributes.default_value.is_some()
}
pub fn clean_name(&self) -> String {
strip_raw_identifier_prefix(&self.name.to_string()).into_owned()
}
pub fn setter_name(&self) -> String {
self.attributes
.setter_name
.as_ref()
.cloned()
.unwrap_or_else(|| self.name.to_string())
}
pub fn final_setter_name(&self, struct_setter_prefix: Option<&str>) -> String {
let base_name = self.setter_name();
if let Some(field_prefix) = &self.attributes().setter_prefix {
format!("{field_prefix}{base_name}")
} else if let Some(struct_prefix) = struct_setter_prefix {
format!("{struct_prefix}{base_name}")
} else {
base_name
}
}
pub fn create_setter_config(&self, struct_setter_prefix: Option<&str>) -> SetterConfig {
let field_name_str = self.name().to_string();
let clean_name = strip_raw_identifier_prefix(&field_name_str);
let setter_name = Cow::Owned(self.final_setter_name(struct_setter_prefix));
let doc_comment = if self.attributes().required {
format!("Sets the required field `{clean_name}`.")
} else {
format!("Sets the optional field `{clean_name}`.")
};
SetterConfig {
setter_name,
_field_name: Cow::Owned(field_name_str.clone()),
_clean_name: clean_name.into_owned().into(),
skip_setter: self.attributes().skip_setter,
doc_comment,
}
}
pub fn create_default_config(&self) -> DefaultConfig {
let has_custom_default = self.attributes().default_value.is_some();
let default_expression = self.attributes().default_value.clone();
let requires_default_trait = !has_custom_default;
DefaultConfig {
_has_custom_default: has_custom_default,
default_expression,
_requires_default_trait: requires_default_trait,
}
}
pub fn generate_initialization(
&self,
is_required_unset: bool,
) -> syn::Result<proc_macro2::TokenStream> {
let field_name = self.name();
if is_required_unset {
Ok(quote! {
#field_name: ::core::option::Option::None,
})
} else {
let default_config = self.create_default_config();
if let Some(default_expr) = default_config.default_expression {
Ok(quote! {
#field_name: #default_expr,
})
} else {
let field_name_str = self.name().to_string();
let clean_name = strip_raw_identifier_prefix(&field_name_str);
let field_type = self.field_type();
let helpful_message = format!(
"Field `{}` does not have a custom default and its type may not implement `Default`. \
Solutions: \
1. Add `#[builder(default = \"...\")]` with a custom default value, \
2. Ensure the field type implements `Default`, or \
3. Add `#[derive(Default)]` to your custom types.",
&*clean_name
);
Ok(quote! {
#field_name: {
#[allow(unused)]
const _HELP: &str = #helpful_message;
<#field_type as ::core::default::Default>::default()
},
})
}
}
}
pub fn generate_setter_method(
&self,
return_type: &Type,
struct_setter_prefix: Option<&str>,
struct_impl_into: bool,
is_const: bool,
) -> syn::Result<proc_macro2::TokenStream> {
use crate::utils::field_utils::{extract_closure_info, generate_const_converter_fn_name};
let config = self.create_setter_config(struct_setter_prefix);
if config.skip_setter {
return Ok(quote! {});
}
let field_name = self.name();
let field_type = self.field_type();
let field_impl_into = self.attributes().impl_into;
let converter = self.attributes().converter.as_ref();
let use_impl_into = if is_const {
false
} else {
resolve_effective_impl_into(field_impl_into, struct_impl_into)
};
let setter_ident = if config.setter_name.starts_with("r#") {
syn::parse_str::<Ident>(&config.setter_name)?
} else {
syn::parse_str::<Ident>(&config.setter_name)?
};
let doc_comment = &config.doc_comment;
if is_const {
if let Some(converter_expr) = converter {
if let Some(closure_info) = extract_closure_info(converter_expr) {
let const_fn_name = generate_const_converter_fn_name(&self.clean_name());
let param_name = closure_info.param_name;
let param_type = closure_info.param_type;
let body = closure_info.body;
return Ok(quote! {
#[doc(hidden)]
const fn #const_fn_name(#param_name: #param_type) -> #field_type {
#body
}
#[doc = #doc_comment]
pub const fn #setter_ident(self, value: #param_type) -> #return_type {
Self { #field_name: Self::#const_fn_name(value), ..self }
}
});
}
}
let param_config = resolve_setter_parameter_config(field_type, None, false);
let param_type = param_config.param_type;
let field_assignment_expr = param_config.field_assignment_expr;
Ok(quote! {
#[doc = #doc_comment]
pub const fn #setter_ident(self, value: #param_type) -> #return_type {
Self { #field_name: #field_assignment_expr, ..self }
}
})
} else {
let param_config =
resolve_setter_parameter_config(field_type, converter, use_impl_into);
let param_type = param_config.param_type;
let field_assignment_expr = param_config.field_assignment_expr;
Ok(quote! {
#[doc = #doc_comment]
pub fn #setter_ident(mut self, value: #param_type) -> #return_type {
self.#field_name = #field_assignment_expr;
self
}
})
}
}
pub fn validate_configuration(&self) -> syn::Result<()> {
if self.is_required() && self.attributes().default_value.is_some() {
return Err(ErrorMessages::structured_error(
self.name(),
"Required fields cannot have default values",
Some("#[builder(default)] and #[builder(required)] are incompatible"),
Some("remove one of incompatible attributes"),
));
}
if self.is_required() && self.attributes().skip_setter {
return Err(ErrorMessages::structured_error(
self.name(),
"Required fields cannot skip setters",
Some("#[builder(skip_setter)] and #[builder(required)] are incompatible"),
Some("remove one of incompatible attributes"),
));
}
if self.attributes().skip_setter && self.attributes().default_value.is_none() {
return Err(ErrorMessages::structured_error(
self.name(),
"Fields with #[builder(skip_setter)] must have a default value",
Some("#[builder(skip_setter)] requires a way to initialize the field"),
Some("add #[builder(default = \"...\")] or remove skip_setter"),
));
}
if let Some(setter_name) = &self.attributes().setter_name {
syn::parse_str::<Ident>(setter_name).map_err(|_| {
syn::Error::new_spanned(
self.name(),
format!("Invalid setter name '{setter_name}'. Setter names must be valid Rust identifiers.")
)
})?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_from_syn_field_required() {
let attrs = vec![parse_quote!(#[builder(required)])];
let field =
FieldInfo::from_syn_field(parse_quote!(name), parse_quote!(String), &attrs).unwrap();
assert!(field.is_required());
assert!(!field.is_optional());
assert_eq!(field.name().to_string(), "name");
}
#[test]
fn test_from_syn_field_optional() {
let field =
FieldInfo::from_syn_field(parse_quote!(age), parse_quote!(Option<u32>), &[]).unwrap();
assert!(!field.is_required());
assert!(field.is_optional());
}
#[test]
fn test_from_syn_field_with_custom_default() {
let attrs = vec![parse_quote!(#[builder(default = "42")])];
let field =
FieldInfo::from_syn_field(parse_quote!(count), parse_quote!(i32), &attrs).unwrap();
assert!(field.has_custom_default());
assert!(field.attributes().default_value.is_some());
}
#[test]
fn test_from_syn_field_skip_setter() {
let attrs = vec![parse_quote!(#[builder(default = "Uuid::new_v4()", skip_setter)])];
let field =
FieldInfo::from_syn_field(parse_quote!(id), parse_quote!(Uuid), &attrs).unwrap();
assert!(!field.should_generate_setter());
assert!(field.has_custom_default());
}
#[test]
fn test_clean_name() {
let regular_field =
FieldInfo::from_syn_field(parse_quote!(user_name), parse_quote!(String), &[]).unwrap();
assert_eq!(regular_field.clean_name(), "user_name");
let raw_field =
FieldInfo::from_syn_field(parse_quote!(r#type), parse_quote!(String), &[]).unwrap();
assert_eq!(raw_field.clean_name(), "type");
}
#[test]
fn test_setter_name() {
let default_field =
FieldInfo::from_syn_field(parse_quote!(user_name), parse_quote!(String), &[]).unwrap();
assert_eq!(default_field.setter_name(), "user_name");
let custom_attrs = vec![parse_quote!(#[builder(setter_name = "set_name")])];
let custom_field = FieldInfo::from_syn_field(
parse_quote!(internal_name),
parse_quote!(String),
&custom_attrs,
)
.unwrap();
assert_eq!(custom_field.setter_name(), "set_name");
}
#[test]
fn test_create_setter_config() {
let field = FieldInfo::from_syn_field(
parse_quote!(name),
parse_quote!(String),
&[parse_quote!(#[builder(required)])],
)
.unwrap();
let config = field.create_setter_config(None);
assert_eq!(config.setter_name, "name");
assert!(!config.skip_setter);
assert!(config.doc_comment.contains("required"));
}
#[test]
fn test_create_default_config() {
let custom_attrs = vec![parse_quote!(#[builder(default = "42")])];
let field =
FieldInfo::from_syn_field(parse_quote!(count), parse_quote!(i32), &custom_attrs)
.unwrap();
let config = field.create_default_config();
assert!(config._has_custom_default);
assert!(config.default_expression.is_some());
}
#[test]
fn test_validation_errors() {
let invalid_attrs = vec![
parse_quote!(#[builder(required)]),
parse_quote!(#[builder(default = "test")]),
];
let result =
FieldInfo::from_syn_field(parse_quote!(name), parse_quote!(String), &invalid_attrs);
assert!(result.is_err());
}
#[test]
fn test_final_setter_name_with_no_prefix() {
let field =
FieldInfo::from_syn_field(parse_quote!(user_name), parse_quote!(String), &[]).unwrap();
assert_eq!(field.final_setter_name(None), "user_name");
assert_eq!(field.final_setter_name(Some("with_")), "with_user_name");
}
#[test]
fn test_final_setter_name_with_custom_setter_name() {
let attrs = vec![parse_quote!(#[builder(setter_name = "set_name")])];
let field =
FieldInfo::from_syn_field(parse_quote!(internal_name), parse_quote!(String), &attrs)
.unwrap();
assert_eq!(field.final_setter_name(None), "set_name");
assert_eq!(field.final_setter_name(Some("with_")), "with_set_name");
}
#[test]
fn test_final_setter_name_with_field_level_prefix() {
let attrs = vec![parse_quote!(#[builder(setter_prefix = "set_")])];
let field =
FieldInfo::from_syn_field(parse_quote!(name), parse_quote!(String), &attrs).unwrap();
assert_eq!(field.final_setter_name(None), "set_name");
assert_eq!(field.final_setter_name(Some("with_")), "set_name");
}
#[test]
fn test_final_setter_name_with_field_prefix_and_custom_name() {
let attrs = vec![
parse_quote!(#[builder(setter_name = "data")]),
parse_quote!(#[builder(setter_prefix = "set_")]),
];
let field =
FieldInfo::from_syn_field(parse_quote!(internal_value), parse_quote!(String), &attrs)
.unwrap();
assert_eq!(field.final_setter_name(None), "set_data");
assert_eq!(field.final_setter_name(Some("with_")), "set_data");
}
#[test]
fn test_create_setter_config_with_prefixes() {
let field =
FieldInfo::from_syn_field(parse_quote!(name), parse_quote!(String), &[]).unwrap();
let config = field.create_setter_config(Some("with_"));
assert_eq!(config.setter_name, "with_name");
let attrs = vec![parse_quote!(#[builder(setter_prefix = "set_")])];
let field_with_prefix =
FieldInfo::from_syn_field(parse_quote!(name), parse_quote!(String), &attrs).unwrap();
let config = field_with_prefix.create_setter_config(Some("with_"));
assert_eq!(config.setter_name, "set_name");
}
}