#[derive(Debug, PartialEq, Eq, derive_new::new)]
pub struct Model<'a> {
pub ident: &'a syn::Ident,
#[new(default)]
pub fields: Vec<(syn::Ident, &'a syn::Type)>,
}
pub fn analyze<'a>(
parsed: &'a crate::parse::merge_patch_nullable_fields::Parsed<'_>,
) -> syn::Result<Model<'a>> {
let mut model = Model::new(parsed.ident);
let mut errors = crate::errors::Errors::default();
for field in &parsed.named_fields.named {
let Some(field_ident) = &field.ident else {
continue;
};
if errors.check(crate::attributes::is_skipped(field, "merge_patch")) == Some(false)
&& errors
.check(crate::attributes::reject_option_of_option(field))
.is_some()
{
model.fields.push((field_ident.clone(), &field.ty));
}
}
errors.finish(model)
}
#[cfg(test)]
mod tests {
fn field_names(named_fields_input: proc_macro2::TokenStream) -> syn::Result<Vec<String>> {
let ident = "e::format_ident!("MergePatchUserModel");
let named_fields = &syn::parse2(named_fields_input).unwrap();
let parsed = crate::parse::merge_patch_nullable_fields::Parsed {
ident,
named_fields,
};
Ok(super::analyze(&parsed)?
.fields
.into_iter()
.map(|(field, _)| field.to_string())
.collect())
}
#[test]
fn test_every_field_but_the_skipped_ones_is_kept() {
let fields = field_names(quote::quote! {
{
#[merge_patch(skip)]
id: UserId,
number: Patch<String>,
capacity: Option<i32>,
client: Patch<UserClient<String>>,
}
})
.unwrap();
pretty_assertions::assert_eq!(fields, ["number", "capacity", "client"]);
}
#[test]
fn test_a_malformed_skip_is_rejected() {
for attribute in [
quote::quote!(#[merge_patch()]),
quote::quote!(#[merge_patch(keep)]),
] {
assert!(field_names(quote::quote!({ #attribute id: UserId })).is_err());
}
}
#[test]
fn test_option_of_option_is_rejected() {
assert!(field_names(quote::quote!({ code: Option<Option<String>> })).is_err());
}
}