use proc_macro2::TokenStream;
use syn::punctuated::Punctuated;
use crate::attr::FieldAttr;
pub(crate) fn for_struct(ast: &syn::DeriveInput, fields: &syn::Fields) -> syn::Result<TokenStream> {
match *fields {
syn::Fields::Named(ref fields) => table_value_param_impl(ast, Some(&fields.named)),
_ => Err(syn::Error::new_spanned(
&ast.ident,
"TableValueRow can only be derived for structs with named fields \
(tuple and unit structs are not supported)",
)),
}
}
fn table_value_param_impl(
ast: &syn::DeriveInput,
fields: Option<&Punctuated<syn::Field, Token![,]>>,
) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (lt_impl, lt_struct) = {
let mut lifetimes: Vec<&syn::Ident> = Vec::new();
for gp in ast.generics.params.iter() {
if let syn::GenericParam::Lifetime(ltp) = gp {
lifetimes.push(<p.lifetime.ident);
}
}
if lifetimes.len() > 1 {
return Err(syn::Error::new_spanned(
&ast.generics,
format!(
"TableValueRow supports at most one lifetime parameter, found: {}",
lifetimes
.iter()
.map(|lt| lt.to_string())
.collect::<Vec<_>>()
.join(", ")
),
));
}
if lifetimes.is_empty() {
(sp_quote!(<'query>), sp_quote!())
} else {
let lt = lifetimes[0];
let ts: proc_macro2::TokenStream = format!("< '{} >", lt).parse().unwrap();
(ts.clone(), ts)
}
};
let empty = Default::default();
let fields: Vec<_> = fields
.unwrap_or(&empty)
.iter()
.map(FieldExt::new)
.collect::<syn::Result<_>>()?;
let col_names: Vec<_> = fields.iter().map(|f| f.get_col_name()).collect();
let _col_names = sp_quote!( #(#col_names),* );
let col_binds: Vec<_> = fields.iter().map(|f| f.as_bind()).collect();
let col_binds = sp_quote!( #(#col_binds);*);
Ok(sp_quote! {
impl #lt_impl tds_driver::TableValueRow #lt_impl for #name #lt_struct {
fn get_db_type() -> &'static str {
stringify!{ #name }
}
fn bind_fields(&self, data_row: &mut tds_driver::SqlTableDataRow #lt_impl) {
#col_binds;
}
}
})
}
struct FieldExt {
attr: Option<FieldAttr>,
ident: syn::Ident,
}
impl FieldExt {
pub fn new(field: &syn::Field) -> syn::Result<FieldExt> {
match field.ident.clone() {
Some(ident) => Ok(FieldExt {
attr: FieldAttr::parse(&field.attrs),
ident,
}),
None => Err(syn::Error::new_spanned(
field,
"TableValueRow fields must be named",
)),
}
}
pub(crate) fn get_col_name(&self) -> String {
if let Some(attr) = self.attr.as_ref() {
if let Some(colname) = attr.colname.as_ref() {
return colname.to_string();
}
}
self.ident.to_string()
}
pub(crate) fn as_bind(&self) -> TokenStream {
let name = &self.ident;
sp_quote!(data_row.add_field(self.#name))
}
}
#[cfg(test)]
mod tests {
use super::for_struct;
#[test]
fn basic_nolifetime() {
let ast: syn::DeriveInput = syn::parse_str(
r#"
pub struct SomeGeoList {
#[colname = "SomeID"]
pub id: i32,
#[colname = "LastSyncIPGeoLat"]
pub lat: Numeric, // decimal(9,6)
#[colname = "LastSyncIPGeoLong"]
pub lon: Numeric, // decimal(9,6)
}
"#,
)
.unwrap();
let result = match ast.data {
syn::Data::Enum(_) => panic!("n/a for enums, makes sense for structs only"),
syn::Data::Struct(ref s) => for_struct(&ast, &s.fields).unwrap(),
syn::Data::Union(_) => panic!("doesn't work with unions"),
};
let etalon = sp_quote!(
impl<'query> tds_driver::TableValueRow<'query> for SomeGeoList {
fn get_db_type() -> &'static str {
stringify! { SomeGeoList }
}
fn bind_fields(&self, data_row: &mut tds_driver::SqlTableDataRow<'query>) {
data_row.add_field(self.id);
data_row.add_field(self.lat);
data_row.add_field(self.lon);
}
}
);
assert_eq!(result.to_string(), etalon.to_string());
}
#[test]
fn basic_lifetime() {
let ast: syn::DeriveInput = syn::parse_str(
r#"
pub struct AnotherGeoList<'e> {
#[colname = "SomeID"]
pub id: i32,
#[colname = "SomeStr"]
pub s: &'e str,
}
"#,
)
.unwrap();
let result = match ast.data {
syn::Data::Enum(_) => panic!("n/a for enums, makes sense for structs only"),
syn::Data::Struct(ref s) => for_struct(&ast, &s.fields).unwrap(),
syn::Data::Union(_) => panic!("doesn't work with unions"),
};
let etalon = sp_quote!(
impl<'e> tds_driver::TableValueRow<'e> for AnotherGeoList<'e> {
fn get_db_type() -> &'static str {
stringify! { AnotherGeoList }
}
fn bind_fields(&self, data_row: &mut tds_driver::SqlTableDataRow<'e>) {
data_row.add_field(self.id);
data_row.add_field(self.s);
}
}
);
assert_eq!(result.to_string(), etalon.to_string());
}
}