use std::collections::{HashMap, hash_map::Entry};
use super::util::GetMeta;
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, quote};
use syn::{
Data, DataStruct, DeriveInput, Error, Fields, Generics, Meta, ext::IdentExt,
punctuated::Punctuated, token::Comma,
};
pub(super) enum ItemType {
Flat,
Skip,
Nested { prefix: Option<String> },
}
pub(super) struct DeriveFromQueryResult {
pub ident: syn::Ident,
pub generics: Generics,
pub fields: Vec<FromQueryResultItem>,
}
pub(super) struct FromQueryResultItem {
pub typ: ItemType,
pub ident: Ident,
pub alias: Option<String>,
}
struct TryFromQueryResultCheck<'a>(bool, &'a FromQueryResultItem);
impl ToTokens for TryFromQueryResultCheck<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let FromQueryResultItem { ident, typ, alias } = self.1;
match typ {
ItemType::Flat => {
let name = alias
.to_owned()
.unwrap_or_else(|| ident.unraw().to_string());
tokens.extend(quote! {
let #ident = match row.try_get_nullable(pre, #name) {
Err(v @ sea_orm::TryGetError::DbErr(_)) => {
return Err(v);
}
v => v,
};
});
}
ItemType::Skip => {
tokens.extend(quote! {
let #ident = std::default::Default::default();
});
}
ItemType::Nested { prefix } => {
let prefix = match (self.0, prefix) {
(_, Some(p)) => quote! { &format!("{pre}{}", #p) },
(true, None) => {
let name = ident.unraw().to_string();
quote! { &format!("{pre}{}_", #name) }
}
(false, None) => quote! { pre },
};
tokens.extend(quote! {
let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(row, #prefix) {
Err(v @ sea_orm::TryGetError::DbErr(_)) => {
return Err(v);
}
v => v,
};
});
}
}
}
}
struct TryFromQueryResultAssignment<'a>(&'a FromQueryResultItem);
impl ToTokens for TryFromQueryResultAssignment<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let FromQueryResultItem { ident, typ, .. } = self.0;
match typ {
ItemType::Flat | ItemType::Nested { .. } => {
tokens.extend(quote! {
#ident: #ident?,
});
}
ItemType::Skip => {
tokens.extend(quote! {
#ident,
});
}
}
}
}
impl DeriveFromQueryResult {
fn new(
DeriveInput {
ident,
data,
generics,
..
}: DeriveInput,
) -> Result<Self, Error> {
let parsed_fields = match data {
Data::Struct(DataStruct {
fields: Fields::Named(named),
..
}) => named.named,
_ => {
return Err(Error::new(
ident.span(),
"you can only derive `FromQueryResult` on named struct",
));
}
};
let mut fields = Vec::with_capacity(parsed_fields.len());
let mut seen_nested: HashMap<(syn::Type, Option<String>), TokenStream> = HashMap::new();
for parsed_field in parsed_fields {
let mut typ = ItemType::Flat;
let mut alias = None;
for attr in parsed_field.attrs.iter() {
if !attr.path().is_ident("sea_orm") {
continue;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
{
for meta in list.iter() {
if meta.exists("skip") {
typ = ItemType::Skip;
} else if meta.exists("nested") {
typ = ItemType::Nested { prefix: None };
} else if let Some(list) = meta.get_list_args("nested") {
let mut prefix = None;
for m in list.iter() {
match m.get_as_kv("prefix") {
Some(p) => prefix = (!p.is_empty()).then_some(p),
None => {
return Err(Error::new_spanned(
m,
"invalid nested attribute, expected `prefix = \"...\"`",
));
}
}
}
typ = ItemType::Nested { prefix };
} else {
alias = meta
.get_as_kv("from_alias")
.or_else(|| meta.get_as_kv("alias"));
}
}
}
}
let field_tokens = parsed_field.to_token_stream();
let ident = parsed_field.ident.unwrap();
if let ItemType::Nested {
prefix: Some(prefix),
} = &typ
{
let key = (parsed_field.ty, Some(prefix.clone()));
match seen_nested.entry(key) {
Entry::Occupied(e) => {
let msg = format!(
"multiple nested fields with the same type share prefix \"{prefix}\""
);
let mut err = Error::new_spanned(&field_tokens, msg);
err.combine(Error::new_spanned(e.get(), "first defined here"));
return Err(err);
}
Entry::Vacant(e) => {
e.insert(field_tokens);
}
}
}
fields.push(FromQueryResultItem { typ, ident, alias });
}
Ok(Self {
ident,
generics,
fields,
})
}
fn expand(&self) -> syn::Result<TokenStream> {
Ok(self.impl_from_query_result(false))
}
pub(super) fn impl_from_query_result(&self, prefix: bool) -> TokenStream {
let Self {
ident,
generics,
fields,
} = self;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let ident_try_init: Vec<_> = fields
.iter()
.map(|s| TryFromQueryResultCheck(prefix, s))
.collect();
let ident_try_assign: Vec<_> = fields.iter().map(TryFromQueryResultAssignment).collect();
quote!(
#[automatically_derived]
impl #impl_generics sea_orm::FromQueryResult for #ident #ty_generics #where_clause {
fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result<Self, sea_orm::DbErr> {
Ok(Self::from_query_result_nullable(row, pre)?)
}
fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result<Self, sea_orm::TryGetError> {
#(#ident_try_init)*
Ok(Self {
#(#ident_try_assign)*
})
}
}
)
}
}
pub fn expand_derive_from_query_result(input: DeriveInput) -> syn::Result<TokenStream> {
DeriveFromQueryResult::new(input)?.expand()
}