use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Field, Ident, Type};
fn impl_try_from_row_by_index(ast: DeriveInput) -> proc_macro2::TokenStream {
let ident: Ident = ast.ident;
let mut fields: Vec<Field> = vec![];
match ast.data {
syn::Data::Struct(data) => {
for field in data.fields {
if field.ident.is_some() {
fields.push(field)
}
}
}
_ => panic!("turso_mappers::TryFromRowByIndex only supports structs"),
};
let field_mappers: Vec<proc_macro2::TokenStream> = fields
.into_iter()
.enumerate()
.map(|(idx, field)| {
let f_ident = field.ident.unwrap();
let f_type = field.ty.clone();
if let Some(inner_type) = get_option_inner_type(&f_type) {
return match inner_type.as_str() {
"i64" => quote! {
#f_ident: match row.get_value(#idx) {
Ok(value) => match value.as_integer() {
Some(val) => Some(*val),
None => None,
},
Err(_) => None,
}
},
"String" => quote! {
#f_ident: match row.get_value(#idx) {
Ok(value) => match value.as_text() {
Some(val) => Some(val.clone()),
None => None,
},
Err(_) => None,
}
},
"f64" => quote! {
#f_ident: match row.get_value(#idx) {
Ok(value) => match value.as_real() {
Some(val) => Some(*val),
None => None,
},
Err(_) => None,
}
},
"Vec<u8>" => quote! {
#f_ident: match row.get_value(#idx) {
Ok(value) => match value.as_blob() {
Some(val) => Some(val.clone()),
None => None,
},
Err(_) => None,
}
},
_ => {
let error_msg = format!("Unsupported Option type: Option<{}>", inner_type);
quote! {
#f_ident: compile_error!(#error_msg)
}
}
};
}
let type_path = get_type_path(&f_type);
match type_path.as_str() {
"i64" => quote! {
#f_ident: *row
.get_value(#idx)?
.as_integer()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not an integer", stringify!(#f_ident))))?
},
"String" => quote! {
#f_ident: row
.get_value(#idx)?
.as_text()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not a string", stringify!(#f_ident))))?
.clone()
},
"f64" => quote! {
#f_ident: *row
.get_value(#idx)?
.as_real()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not a real", stringify!(#f_ident))))?
},
"Vec<u8>" => quote! {
#f_ident: row
.get_value(#idx)?
.as_blob()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not a blob", stringify!(#f_ident))))?
.clone()
},
_ => {
let error_msg = format!("Unsupported type: {}", type_path);
quote! {
#f_ident: compile_error!(#error_msg)
}
}
}
})
.collect::<Vec<_>>();
quote! {
impl turso_mappers::TryFromRowByIndex for #ident {
fn try_from_row_by_index(row: turso::Row) -> turso_mappers::TursoMapperResult<Self> where Self: Sized {
Ok(Self {
#(#field_mappers,)*
})
}
}
}
}
fn get_type_path(ty: &Type) -> String {
match ty {
Type::Path(type_path) if !type_path.path.segments.is_empty() => {
let segment = &type_path.path.segments[0];
let ident = segment.ident.to_string();
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if !args.args.is_empty() {
if let Some(syn::GenericArgument::Type(Type::Path(inner_path))) = args.args.first() {
if !inner_path.path.segments.is_empty() {
let inner_type = inner_path.path.segments[0].ident.to_string();
return format!("{}<{}>", ident, inner_type);
}
}
}
}
ident
}
_ => "unknown".to_string(),
}
}
fn get_option_inner_type(ty: &Type) -> Option<String> {
match ty {
Type::Path(type_path) if !type_path.path.segments.is_empty() => {
let segment = &type_path.path.segments[0];
let ident = segment.ident.to_string();
if ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if !args.args.is_empty() {
if let Some(syn::GenericArgument::Type(inner_type)) = args.args.first() {
return Some(get_type_path(inner_type));
}
}
}
}
None
}
_ => None,
}
}
fn impl_try_from_row_by_name(ast: DeriveInput) -> proc_macro2::TokenStream {
let ident: Ident = ast.ident;
let mut fields: Vec<Field> = vec![];
match ast.data {
syn::Data::Struct(data) => {
for field in data.fields {
if field.ident.is_some() {
fields.push(field)
}
}
}
_ => panic!("turso_mappers::TryFromRowByName only supports structs"),
};
let field_count = fields.len();
let resolve_lookups: Vec<proc_macro2::TokenStream> = fields
.iter()
.map(|field| {
let f_name = field.ident.as_ref().unwrap().to_string();
quote! { column_indices.get_index(#f_name)? }
})
.collect();
let field_mappers: Vec<proc_macro2::TokenStream> = fields
.into_iter()
.enumerate()
.map(|(idx, field)| {
let f_ident = field.ident.unwrap();
let f_type = field.ty.clone();
if let Some(inner_type) = get_option_inner_type(&f_type) {
return match inner_type.as_str() {
"i64" => quote! {
#f_ident: match row.get_value(indices[#idx]) {
Ok(value) => match value.as_integer() {
Some(val) => Some(*val),
None => None,
},
Err(_) => None,
}
},
"String" => quote! {
#f_ident: match row.get_value(indices[#idx]) {
Ok(value) => match value.as_text() {
Some(val) => Some(val.clone()),
None => None,
},
Err(_) => None,
}
},
"f64" => quote! {
#f_ident: match row.get_value(indices[#idx]) {
Ok(value) => match value.as_real() {
Some(val) => Some(*val),
None => None,
},
Err(_) => None,
}
},
"Vec<u8>" => quote! {
#f_ident: match row.get_value(indices[#idx]) {
Ok(value) => match value.as_blob() {
Some(val) => Some(val.clone()),
None => None,
},
Err(_) => None,
}
},
_ => {
let error_msg = format!("Unsupported Option type: Option<{}>", inner_type);
quote! {
#f_ident: compile_error!(#error_msg)
}
}
};
}
let f_name = f_ident.to_string();
let type_path = get_type_path(&f_type);
match type_path.as_str() {
"i64" => quote! {
#f_ident: *row
.get_value(indices[#idx])?
.as_integer()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not an integer", #f_name)))?
},
"String" => quote! {
#f_ident: row
.get_value(indices[#idx])?
.as_text()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not a string", #f_name)))?
.clone()
},
"f64" => quote! {
#f_ident: *row
.get_value(indices[#idx])?
.as_real()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not a real", #f_name)))?
},
"Vec<u8>" => quote! {
#f_ident: row
.get_value(indices[#idx])?
.as_blob()
.ok_or_else(|| turso_mappers::TursoMapperError::ConversionError(format!("{} is not a blob", #f_name)))?
.clone()
},
_ => {
let error_msg = format!("Unsupported type: {}", type_path);
quote! {
#f_ident: compile_error!(#error_msg)
}
}
}
})
.collect::<Vec<_>>();
quote! {
impl turso_mappers::TryFromRowByName for #ident {
type Indices = [usize; #field_count];
fn resolve_indices(column_indices: &turso_mappers::ColumnIndices) -> turso_mappers::TursoMapperResult<Self::Indices> {
Ok([#(#resolve_lookups,)*])
}
fn try_from_row_by_name(row: turso::Row, indices: &Self::Indices) -> turso_mappers::TursoMapperResult<Self> where Self: Sized {
Ok(Self {
#(#field_mappers,)*
})
}
}
}
}
#[proc_macro_derive(TryFromRowByIndex)]
pub fn try_from_row_by_index_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
impl_try_from_row_by_index(ast).into()
}
#[proc_macro_derive(TryFromRowByName)]
pub fn try_from_row_by_name_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
impl_try_from_row_by_name(ast).into()
}