#![recursion_limit = "256"]
#![deny(missing_docs)]
extern crate proc_macro;
use std::path::PathBuf;
use butane_core::migrations::adb::{DeferredSqlType, TypeIdentifier};
use butane_core::{codegen, make_compile_error, migrations, SqlType};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::TokenTree;
use quote::quote;
use syn::{Expr, Ident};
mod filter;
#[proc_macro_attribute]
pub fn model(_args: TokenStream, input: TokenStream) -> TokenStream {
codegen::model_with_migrations(input.into(), &mut migrations_for_dir()).into()
}
#[proc_macro_attribute]
pub fn dataresult(args: TokenStream, input: TokenStream) -> TokenStream {
codegen::dataresult(args.into(), input.into()).into()
}
#[proc_macro]
pub fn filter(input: TokenStream) -> TokenStream {
let input: TokenStream2 = input.into();
let args: Vec<TokenTree> = input.into_iter().collect();
if args.len() < 2 {
return make_compile_error!("Expected filter!(Type, expression)").into();
}
let tyid: Ident = match &args[0] {
TokenTree::Ident(tyid) => tyid.clone(),
TokenTree::Group(g) => match syn::parse2::<Ident>(g.stream()) {
Ok(ident) => ident,
Err(_) => {
return make_compile_error!("Unexpected tokens in database object type {:?}", &g)
.into()
}
},
_ => {
return make_compile_error!("Unexpected tokens in database object type {:?}", &args[0])
.into()
}
};
if matches!(args[1], TokenTree::Punct(_)) {
} else {
return make_compile_error!("Expected filter!(Type, expression)").into();
}
let expr: TokenStream2 = args.into_iter().skip(2).collect();
let expr: Expr = match syn::parse2(expr) {
Ok(expr) => expr,
Err(_) => {
return make_compile_error!(
"Expected filter!(Type, expression) but could not parse expression"
)
.into()
}
};
filter::for_expr(&tyid, &expr).into()
}
#[proc_macro_attribute]
pub fn butane_type(args: TokenStream, input: TokenStream) -> TokenStream {
codegen::butane_type_with_migrations(args.into(), input.into(), &mut migrations_for_dir())
.into()
}
fn migrations_for_dir() -> migrations::FsMigrations {
migrations::from_root(migrations_dir())
}
fn migrations_dir() -> PathBuf {
let mut dir = PathBuf::from(
std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR expected to be set"),
);
dir.push(".butane");
dir.push("migrations");
dir
}
#[proc_macro_derive(FieldType)]
pub fn derive_field_type(input: TokenStream) -> TokenStream {
let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
let ident = &derive_input.ident;
match derive_input.data {
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }),
..
}) => {
if unnamed.len() == 1 {
let field = unnamed.first().unwrap();
let path = codegen::extract_path_from_type(&field.ty);
if let Some(DeferredSqlType::KnownId(TypeIdentifier::Ty(sqltype))) =
codegen::get_primitive_sql_type(path)
{
return derive_field_type_for_newtype(ident, sqltype);
}
}
derive_field_type_with_json(ident)
}
syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named { .. },
..
})
| syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Unit,
..
}) => derive_field_type_with_json(ident),
syn::Data::Enum(data_enum) => derive_field_type_for_enum(ident, data_enum),
syn::Data::Union(_) => derive_field_type_with_json(ident),
}
}
fn derive_field_type_for_newtype(ident: &Ident, sqltype: SqlType) -> TokenStream {
let sqltype_name = serde_variant::to_variant_name(&sqltype).unwrap();
let sqltype_ident = syn::Ident::new(sqltype_name, proc_macro2::Span::call_site());
let mut migrations = migrations_for_dir();
codegen::add_custom_type(
&mut migrations,
ident.to_string(),
DeferredSqlType::KnownId(TypeIdentifier::Ty(sqltype)),
)
.unwrap();
quote!(
impl butane::ToSql for #ident
{
fn to_sql(&self) -> butane::SqlVal {
self.0.to_sql()
}
fn to_sql_ref(&self) -> butane::SqlValRef<'_> {
self.0.to_sql_ref()
}
}
impl butane::FromSql for #ident
{
fn from_sql_ref(val: butane::SqlValRef) -> std::result::Result<Self, butane::Error> {
let inner = butane::FromSql::from_sql_ref(val)?;
Ok(Self ( inner ))
}
}
impl butane::FieldType for #ident
{
type RefType = Self;
const SQLTYPE: butane::SqlType = butane::SqlType:: #sqltype_ident;
}
)
.into()
}
fn derive_field_type_for_enum(ident: &Ident, data_enum: syn::DataEnum) -> TokenStream {
if data_enum
.variants
.iter()
.any(|variant| variant.fields != syn::Fields::Unit)
{
return derive_field_type_with_json(ident);
}
let mut migrations = migrations_for_dir();
codegen::add_custom_type(
&mut migrations,
ident.to_string(),
DeferredSqlType::KnownId(TypeIdentifier::Ty(SqlType::Text)),
)
.unwrap();
let match_arms_to_string: Vec<TokenStream2> = data_enum
.variants
.iter()
.map(|variant| {
let v_ident = &variant.ident;
let ident_literal = codegen::make_ident_literal_str(v_ident);
quote!(Self::#v_ident => #ident_literal,)
})
.collect();
let match_arms_from_string: Vec<TokenStream2> = data_enum
.variants
.iter()
.map(|variant| {
let v_ident = &variant.ident;
let ident_literal = codegen::make_ident_literal_str(v_ident);
quote!(#ident_literal => Ok(Self::#v_ident),)
})
.collect();
quote!(
impl #ident {
fn to_string_for_butane(&self) -> &'static str {
match self {
#(#match_arms_to_string)*
}
}
fn from_string_for_butane(s: &str) -> std::result::Result<Self, butane::Error> {
match s {
#(#match_arms_from_string)*
_ => Err(butane::Error::UnknownEnumVariant(s.to_string()))
}
}
}
impl butane::ToSql for #ident
{
fn to_sql(&self) -> butane::SqlVal {
butane::SqlVal::Text(self.to_string_for_butane().to_string())
}
fn to_sql_ref(&self) -> butane::SqlValRef<'_> {
butane::SqlValRef::Text(self.to_string_for_butane())
}
}
impl butane::FromSql for #ident
{
fn from_sql_ref(val: butane::SqlValRef) -> std::result::Result<Self, butane::Error> {
if let butane::SqlValRef::Text(v) = val {
return Self::from_string_for_butane(v);
}
Err(butane::Error::CannotConvertSqlVal(
butane::SqlType::Text,
val.into(),
))
}
}
impl butane::FieldType for #ident
{
type RefType = Self;
const SQLTYPE: butane::SqlType = butane::SqlType::Text;
}
)
.into()
}
#[cfg(feature = "json")]
fn derive_field_type_with_json(struct_name: &Ident) -> TokenStream {
let mut migrations = migrations_for_dir();
codegen::add_custom_type(
&mut migrations,
struct_name.to_string(),
DeferredSqlType::KnownId(TypeIdentifier::Ty(SqlType::Json)),
)
.unwrap();
quote!(
impl butane::ToSql for #struct_name
{
fn to_sql(&self) -> butane::SqlVal {
self.to_sql_ref().into()
}
fn to_sql_ref(&self) -> butane::SqlValRef<'_> {
butane::SqlValRef::Json(serde_json::to_value(self).unwrap())
}
}
impl butane::FromSql for #struct_name
{
fn from_sql_ref(val: butane::SqlValRef) -> std::result::Result<Self, butane::Error> {
if let butane::SqlValRef::Json(v) = val {
use ::serde::Deserialize;
return Ok(#struct_name::deserialize(v).unwrap());
}
Err(butane::Error::CannotConvertSqlVal(
butane::SqlType::Json,
val.into(),
))
}
}
impl butane::FieldType for #struct_name
{
type RefType = Self;
const SQLTYPE: butane::SqlType = butane::SqlType::Json;
}
)
.into()
}
#[cfg(not(feature = "json"))]
fn derive_field_type_with_json(_struct_name: &Ident) -> TokenStream {
panic!("Feature 'json' is required to derive FieldType")
}
#[proc_macro_derive(PrimaryKeyType)]
pub fn derive_primary_key_type(input: TokenStream) -> TokenStream {
let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
let ident = &derive_input.ident;
quote!(
impl butane::PrimaryKeyType for #ident {}
)
.into()
}