use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Ident;
use crate::entity::parse::{FieldDef, MapConfig, UuidVersion};
pub fn assigns(fields: &[FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.map(|f: &FieldDef| {
let name = f.name();
quote! { #name: #src.#name }
})
.collect()
}
pub fn assigns_clone(fields: &[FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.map(|f: &FieldDef| {
let name = f.name();
quote! { #name: #src.#name.clone() }
})
.collect()
}
pub fn assigns_from_refs(fields: &[&FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.map(|f: &&FieldDef| {
let name = f.name();
quote! { #name: #src.#name }
})
.collect()
}
pub fn assigns_clone_from_refs(fields: &[&FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.map(|f: &&FieldDef| {
let name = f.name();
quote! { #name: #src.#name.clone() }
})
.collect()
}
pub fn create_assigns(
all_fields: &[FieldDef],
create_fields: &[&FieldDef],
uuid_version: UuidVersion
) -> Vec<TokenStream> {
all_fields
.iter()
.map(|f: &FieldDef| {
let name = f.name();
let is_in_create = create_fields.iter().any(|cf: &&FieldDef| cf.name() == name);
if is_in_create {
quote! { #name: dto.#name }
} else if f.is_id() {
match uuid_version {
UuidVersion::V7 => quote! { #name: uuid::Uuid::now_v7() },
UuidVersion::V4 => quote! { #name: uuid::Uuid::new_v4() }
}
} else {
quote! { #name: Default::default() }
}
})
.collect()
}
pub fn row_assigns(fields: &[FieldDef], source: &str) -> Vec<TokenStream> {
let src = Ident::new(source, Span::call_site());
fields
.iter()
.map(|f: &FieldDef| {
let name = f.name();
let map = f.map();
if matches!(map, MapConfig::None) {
quote! { #name: #src.#name }
} else {
let expr = map.generate(name, &src);
quote! { #name: #expr }
}
})
.collect()
}
#[cfg(test)]
mod tests {
use quote::quote;
use syn::parse_quote;
use super::*;
fn make_field(tokens: proc_macro2::TokenStream) -> FieldDef {
let field: syn::Field = parse_quote!(#tokens);
FieldDef::from_field(&field).unwrap()
}
#[test]
fn row_assigns_simple() {
let fields = vec![make_field(quote::quote! { pub name: String })];
let assigns = row_assigns(&fields, "row");
assert_eq!(assigns.len(), 1);
let expected = quote! { name: row.name };
assert_eq!(assigns[0].to_string(), expected.to_string());
}
#[test]
fn row_assigns_multiple() {
let fields = vec![
make_field(quote::quote! { pub id: uuid::Uuid }),
make_field(quote::quote! { pub name: String }),
];
let assigns = row_assigns(&fields, "row");
assert_eq!(assigns.len(), 2);
}
#[test]
fn row_assigns_empty_to_none() {
let field = make_field(quote::quote! {
#[map(empty_to_none)]
pub nickname: Option<String>
});
let assigns = row_assigns(&[field], "row");
let expected = quote! { nickname: row.nickname.filter(|s| !s.is_empty()) };
assert_eq!(assigns[0].to_string(), expected.to_string());
}
#[test]
fn row_assigns_unwrap_default() {
let field = make_field(quote::quote! {
#[map(unwrap_default)]
pub age: Option<i32>
});
let assigns = row_assigns(&[field], "row");
let expected = quote! { age: Some(row.age.unwrap_or_default()) };
assert_eq!(assigns[0].to_string(), expected.to_string());
}
#[test]
fn row_assigns_now() {
let field = make_field(quote::quote! {
#[map(now)]
pub last_seen: Option<chrono::DateTime<chrono::Utc>>
});
let assigns = row_assigns(&[field], "row");
let expected = quote! { last_seen: Some(row.last_seen.unwrap_or_else(chrono::Utc::now)) };
assert_eq!(assigns[0].to_string(), expected.to_string());
}
#[test]
fn row_assigns_expr() {
let field = make_field(quote::quote! {
#[map(expr = "row.raw.parse().unwrap_or(0)")]
pub score: i32
});
let assigns = row_assigns(&[field], "row");
let expected = quote! { score: row.raw.parse().unwrap_or(0) };
assert_eq!(assigns[0].to_string(), expected.to_string());
}
#[test]
fn row_assigns_mixed() {
let fields = vec![
make_field(quote::quote! { pub id: uuid::Uuid }),
make_field(quote::quote! {
#[map(empty_to_none)]
pub nickname: Option<String>
}),
make_field(quote::quote! { pub email: String }),
];
let assigns = row_assigns(&fields, "row");
assert_eq!(assigns.len(), 3);
let expected_id = quote! { id: row.id };
let expected_nickname = quote! { nickname: row.nickname.filter(|s| !s.is_empty()) };
let expected_email = quote! { email: row.email };
assert_eq!(assigns[0].to_string(), expected_id.to_string());
assert_eq!(assigns[1].to_string(), expected_nickname.to_string());
assert_eq!(assigns[2].to_string(), expected_email.to_string());
}
#[test]
fn row_assigns_source_name() {
let fields = vec![make_field(quote::quote! { pub name: String })];
let assigns = row_assigns(&fields, "src");
let expected = quote! { name: src.name };
assert_eq!(assigns[0].to_string(), expected.to_string());
}
}