use crate::input_and_compile_error;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{ItemFn, LitInt, LitStr, Token};
macro_rules! job_args_parse {
(
$($name:ident, $trigger_type:ident, $lower:ident, $trigger_runtime_type:ty,)+
) => {
pub(crate) enum JobType {
$(
$name,
)+
}
impl JobType {
fn parse_args(self, args: TokenStream) -> syn::Result<JobArgs> {
match self {
$(
Self::$name => syn::parse::<$name>(args).map(JobArgs::from),
)+
}
}
}
pub enum JobArgs {
$(
$name($trigger_type),
)+
}
$(
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct $name($trigger_type);
impl syn::parse::Parse for $name {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let trigger = input.parse::<syn::$trigger_type>().map_err(|mut err| {
err.combine(syn::Error::new(
err.span(),
concat!("invalid job definition, expected #[", stringify!($lower), "(", stringify!($trigger_runtime_type), ")]"),
));
err
})?;
if input.peek(Token![,]) {
return Err(syn::Error::new(
Span::call_site(),
"Unknown attribute key is specified",
));
}
Ok($name(trigger))
}
}
impl From<$name> for JobArgs {
fn from($lower: $name) -> Self {
Self::$name($lower.0)
}
}
)+
};
}
#[rustfmt::skip]
job_args_parse!(
OneShot, LitInt, one_shot, u64,
FixDelay, LitInt, fix_delay, u64,
FixRate, LitInt, fix_rate, u64,
Cron, LitStr, cron, str,
);
pub(crate) struct Job {
name: syn::Ident,
args: JobArgs,
ast: syn::ItemFn,
doc_attributes: Vec<syn::Attribute>,
}
impl Job {
fn new(args: JobArgs, ast: ItemFn) -> syn::Result<Self> {
let name = ast.sig.ident.clone();
let doc_attributes = ast
.attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
.cloned()
.collect();
if ast.sig.asyncness.is_none() {
return Err(syn::Error::new_spanned(
ast.sig.fn_token,
"only support async fn",
));
}
Ok(Self {
name,
args,
ast,
doc_attributes,
})
}
}
impl ToTokens for Job {
fn to_tokens(&self, output: &mut TokenStream2) {
let Self {
name,
ast,
args,
doc_attributes,
} = self;
#[allow(unused_variables)] let vis = &ast.vis;
let register_stream = match args {
JobArgs::OneShot(literal) => {
quote! { __jobs.add_job(::spring_job::job::Job::one_shot(#literal).run(#name))}
}
JobArgs::FixDelay(literal) => {
quote! { __jobs.add_job(::spring_job::job::Job::fix_delay(#literal).run(#name))}
}
JobArgs::FixRate(literal) => {
quote! { __jobs.add_job(::spring_job::job::Job::fix_rate(#literal).run(#name))}
}
JobArgs::Cron(literal) => {
quote! { __jobs.add_job(::spring_job::job::Job::cron(#literal).run(#name))}
}
};
let stream = quote! {
#(#doc_attributes)*
#[allow(non_camel_case_types, missing_docs)]
#vis struct #name;
impl ::spring_job::handler::TypedHandlerRegistrar for #name {
fn install_job(&self, mut __jobs: ::spring_job::Jobs) -> ::spring_job::Jobs {
use ::spring_job::JobConfigurator;
use ::spring_job::job::JobBuilder;
#ast
#register_stream
}
}
::spring_job::submit_typed_handler!(#name);
};
output.extend(stream);
}
}
pub(crate) fn with_job(job_type: JobType, args: TokenStream, input: TokenStream) -> TokenStream {
let args = match job_type.parse_args(args) {
Ok(job) => job,
Err(err) => return input_and_compile_error(input, err),
};
let ast = match syn::parse::<syn::ItemFn>(input.clone()) {
Ok(ast) => ast,
Err(err) => return input_and_compile_error(input, err),
};
match Job::new(args, ast) {
Ok(job) => job.into_token_stream().into(),
Err(err) => input_and_compile_error(input, err),
}
}