use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse2, ItemFn, LitInt, Result};
pub fn expand(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
let (from, to) = parse_attr(attr)?;
if to <= from {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
format!(
"#[hopper::migrate] requires to > from (got from={}, to={}). Migrations only move forward.",
from, to
),
));
}
let input: ItemFn = parse2(item)?;
let fn_name = &input.sig.ident;
let vis = &input.vis;
let edge_ident = format_ident!("{}_EDGE", fn_name.to_string().to_uppercase());
let expanded = quote! {
#input
#[doc = concat!(
"Migration edge declared by `#[hopper::migrate(from = ",
stringify!(#from), ", to = ", stringify!(#to), ")]`.\n\n",
"Consumed by `hopper::layout_migrations!` when composing a ",
"layout's `MIGRATIONS` chain."
)]
#vis const #edge_ident: ::hopper::__runtime::MigrationEdge =
::hopper::__runtime::MigrationEdge {
from_epoch: #from,
to_epoch: #to,
migrator: #fn_name,
};
};
Ok(expanded)
}
fn parse_attr(attr: TokenStream) -> Result<(u32, u32)> {
let mut from: Option<u32> = None;
let mut to: Option<u32> = None;
let parser = syn::meta::parser(|meta| {
if meta.path.is_ident("from") {
let lit: LitInt = meta.value()?.parse()?;
from = Some(lit.base10_parse()?);
return Ok(());
}
if meta.path.is_ident("to") {
let lit: LitInt = meta.value()?.parse()?;
to = Some(lit.base10_parse()?);
return Ok(());
}
Err(meta
.error("unrecognized #[hopper::migrate] attribute. only `from` and `to` are accepted"))
});
syn::parse::Parser::parse2(parser, attr)?;
let from = from.ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"#[hopper::migrate] requires `from = N`",
)
})?;
let to = to.ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"#[hopper::migrate] requires `to = N`",
)
})?;
Ok((from, to))
}