use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned as _;
use syn::{Data, DeriveInput, Error, Index, Result, Type};
use crate::attr::{combine, reject_lifetimes, reject_options};
pub(crate) fn derive(input: DeriveInput) -> Result<TokenStream> {
let mut errors: Option<Error> = None;
if let Err(e) = reject_options(&input.attrs, "the struct") {
combine(&mut errors, e);
}
let named = input.ident.to_string();
if let Err(e) = reject_lifetimes(&input.generics, |lt| {
format!(
"`#[derive(Bind)]` cannot bind a borrowed type, and `{named}` has the lifetime \
parameter `{}`. `FromValue` builds an owned value out of a `Value` — the row it \
came from is gone by then — so a borrowing type could never read back, and the \
impl this would emit could never apply. Wrap owned data (`String`, `Vec<u8>`, …); \
no public keelson type carries a lifetime, for the same reason",
lt.lifetime
)
}) {
combine(&mut errors, e);
}
let field = match single_field(&input) {
Ok(f) => Some(f),
Err(e) => {
combine(&mut errors, e);
None
}
};
if let Some(f) = &field
&& let Err(e) = reject_options(f.attrs, "the field")
{
combine(&mut errors, e);
}
if let Some(e) = errors {
return Err(e);
}
let Field {
member,
ty,
attrs: _,
} = field.expect("no error means a field");
let name = &input.ident;
let mut generics = input.generics.clone();
if !generics.params.is_empty() {
generics
.make_where_clause()
.predicates
.push(syn::parse_quote!(#ty: ::keelson_core::ToValue + ::keelson_core::FromValue));
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let construct = match &member {
syn::Member::Named(f) => quote!(#name { #f: inner }),
syn::Member::Unnamed(_) => quote!(#name(inner)),
};
Ok(quote! {
#[automatically_derived]
impl #impl_generics ::keelson_core::ToValue for #name #ty_generics #where_clause {
fn to_value(self) -> ::keelson_core::Value {
<#ty as ::keelson_core::ToValue>::to_value(self.#member)
}
}
#[automatically_derived]
impl #impl_generics ::keelson_core::FromValue for #name #ty_generics #where_clause {
fn from_value(
value: ::keelson_core::Value,
) -> ::core::result::Result<Self, ::keelson_core::Error> {
<#ty as ::keelson_core::FromValue>::from_value(value)
.map(|inner| #construct)
}
}
})
}
struct Field<'a> {
member: syn::Member,
ty: &'a Type,
attrs: &'a [syn::Attribute],
}
fn single_field(input: &DeriveInput) -> Result<Field<'_>> {
let name = &input.ident;
let fields = match &input.data {
Data::Struct(s) => &s.fields,
Data::Enum(_) => {
return Err(Error::new_spanned(
name,
format!(
"`#[derive(Bind)]` supports single-field newtype structs only, and `{name}` \
is an enum. A column holds one scalar, so an enum first needs a database \
representation chosen deliberately — text or integer, which spelling per \
variant, and what an unrecognised value from the database means. Write \
`impl keelson_core::ToValue for {name}` and `impl keelson_core::FromValue \
for {name}` (about ten lines, and the unknown-variant case becomes \
`keelson_core::Error::type_mismatch`), or derive `Bind` on a newtype over \
the representation and convert at the edges"
),
));
}
Data::Union(_) => {
return Err(Error::new_spanned(
name,
format!(
"`#[derive(Bind)]` supports single-field newtype structs only, and `{name}` \
is a union. Reading a union field is unsafe and which field is live is not \
knowable here, so there is nothing this derive could honestly emit"
),
));
}
};
let mut iter = fields.iter();
let Some(first) = iter.next() else {
return Err(Error::new_spanned(
name,
format!(
"`#[derive(Bind)]` needs exactly one field to bind, and `{name}` has none. Give \
it the value it wraps: `struct {name}(i64);`"
),
));
};
if let Some(extra) = iter.next() {
let count = fields.len();
let mut e = Error::new(
extra.span(),
format!(
"`#[derive(Bind)]` binds one column, so it needs exactly one field, and `{name}` \
has {count}. Bind each part as its own column — a struct of them is a row, not \
a value, and `#[derive(FromRow)]` maps that — or, if the parts really are one \
column, encode them by hand in `impl keelson_core::ToValue for {name}` and the \
matching `FromValue`"
),
);
e.combine(Error::new(
first.span(),
"the first field is here".to_owned(),
));
return Err(e);
}
let member = match &first.ident {
Some(id) => syn::Member::Named(id.clone()),
None => syn::Member::Unnamed(Index {
index: 0,
span: first.span(),
}),
};
Ok(Field {
member,
ty: &first.ty,
attrs: &first.attrs,
})
}