use quote::{quote, quote_spanned};
#[must_use]
pub fn assert_fields_exist<'a>(
target: &syn::Path,
fields: impl IntoIterator<Item = &'a syn::Ident>,
) -> proc_macro2::TokenStream {
let borrows = fields
.into_iter()
.map(|field| quote_spanned!(field.span()=> let _ = &value.#field;))
.collect::<Vec<_>>();
if borrows.is_empty() {
return proc_macro2::TokenStream::new();
}
quote! {
const _: fn(#target) = |value| {
#(#borrows)*
};
}
}
#[cfg(test)]
mod tests {
use super::assert_fields_exist;
use pretty_assertions::assert_eq;
fn path(source: &str) -> syn::Path {
syn::parse_str(source).unwrap()
}
#[test]
fn one_borrow_per_field_on_the_target() {
let fields = [
quote::format_ident!("number"),
quote::format_ident!("client"),
];
let assertion = assert_fields_exist(&path("domain::MergePatchOrderModel"), &fields);
let file = syn::parse2::<syn::File>(assertion).unwrap();
assert_eq!(
prettyplease::unparse(&file),
"const _: fn(domain::MergePatchOrderModel) = |value| {\n let _ = &value.number;\n let _ = &value.client;\n};\n"
);
}
#[test]
fn nothing_is_emitted_without_fields() {
assert!(assert_fields_exist(&path("Model"), &[]).is_empty());
}
#[test]
fn doc_example_assert_fields_exist() {
use crate::checks::assert_fields_exist;
let target = syn::parse_str::<syn::Path>("domain::MergePatchOrderModel").unwrap();
let fields = [
quote::format_ident!("number"),
quote::format_ident!("client"),
];
let assertion = assert_fields_exist(&target, &fields).to_string();
assert!(assertion.contains("value . number"));
assert!(assertion.contains("value . client"));
}
}