use std::fs::{create_dir_all, File};
use std::io::Write;
use case::CaseExt;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{self, Ident};
use quote::{format_ident, quote};
use ergol_core::{Element, Enum};
pub fn generate(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let variants = match &ast.data {
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
_ => panic!("Expected enum"),
};
let variants = variants.iter().map(|x| x.ident.clone()).collect::<Vec<_>>();
let impl_variants = impl_variants(&name, variants.as_slice());
let impl_pg = impl_traits(&name, variants.as_slice());
let json = Element::Enum(Enum {
name: format!("{}", name).to_snake(),
variants: variants
.into_iter()
.map(|x| format!("{}", x).to_snake())
.collect(),
});
create_dir_all("migrations/current").unwrap();
let mut file = File::create(format!("migrations/current/{}.json", &name)).unwrap();
file.write_all(
serde_json::to_string_pretty(&vec![json])
.unwrap()
.as_bytes(),
)
.unwrap();
let q = quote! {
#impl_variants
#impl_pg
};
q.into()
}
pub fn impl_variants(name: &Ident, variants: &[Ident]) -> TokenStream2 {
let type_name = format_ident!("{}", name.to_string().to_snake());
let variants_names = variants
.iter()
.map(|x| x.to_string().to_snake())
.collect::<Vec<_>>();
let create_type = format!(
"CREATE TYPE {} AS ENUM ('{}');",
type_name,
variants_names.join("', '")
);
let drop_type = format!("DROP TYPE {} CASCADE;", type_name);
quote! {
impl #name {
pub fn type_name() -> &'static str {
stringify!(#type_name)
}
pub fn create_type() -> ergol::query::CreateType {
ergol::query::CreateType(vec![String::from(#create_type)])
}
pub fn drop_type() -> ergol::query::DropType {
ergol::query::DropType(vec![String::from(#drop_type)])
}
}
}
}
pub fn impl_traits(name: &Ident, variants: &[Ident]) -> TokenStream2 {
let type_name = format_ident!("{}", name.to_string().to_snake());
let type_name = format!("{} NOT NULL", type_name);
let snake_variants = variants.iter().map(|x| x.to_string().to_snake());
let snake_variants2 = snake_variants.clone();
let impl_pg = quote! {
impl ergol::pg::Pg for #name {
fn ty() -> String {
String::from(#type_name)
}
}
};
let impl_to_sql = quote! {
impl ergol::tokio_postgres::types::ToSql for #name {
fn to_sql(
&self,
ty: &ergol::tokio_postgres::types::Type,
out: &mut ergol::bytes::BytesMut
) -> std::result::Result<ergol::tokio_postgres::types::IsNull, Box<dyn std::error::Error + 'static + Sync + Send>> {
use ergol::bytes::BufMut;
let s = match self {
#(
#name::#variants => #snake_variants,
)*
};
out.put_slice(s.as_bytes());
Ok(ergol::tokio_postgres::types::IsNull::No)
}
fn accepts(ty: &ergol::tokio_postgres::types::Type) -> bool {
true
}
ergol::tokio_postgres::types::to_sql_checked!();
}
};
let impl_from_sql = quote! {
impl<'a> ergol::tokio_postgres::types::FromSql<'a> for #name {
fn from_sql(
ty: &ergol::tokio_postgres::types::Type,
raw: &'a [u8]
) -> std::result::Result<Self, Box<dyn std::error::Error + 'static + Sync + Send>> {
let s = std::str::from_utf8(raw).unwrap();
match s.as_ref() {
#(
#snake_variants2 => Ok(#name::#variants),
)*
_ => unreachable!(),
}
}
fn accepts(ty: &ergol::tokio_postgres::types::Type) -> bool {
true
}
}
};
quote! {
#impl_pg
#impl_to_sql
#impl_from_sql
}
}