batch_codegen/
lib.rs

1//! This crate provides a Batch's derive macro.
2//!
3//! ```rust,ignore
4//! #[derive(Task)]
5//! ```
6
7#![deny(missing_debug_implementations)]
8#![recursion_limit = "128"]
9
10extern crate proc_macro;
11#[macro_use]
12extern crate quote;
13extern crate syn;
14
15use proc_macro::TokenStream;
16use quote::{ToTokens, Tokens};
17use syn::{DeriveInput, Ident, Lit, Meta};
18
19/// Macros 1.1 implementation of `#[derive(Task)]`
20///
21/// This macro supports several attributes:
22///
23/// * `task_name`: a unique ID for the task.
24///   e.g: `#[task_name = "batch-rs:send-confirmation-email"]`
25///   **default value**: The derived struct name
26/// * `task_exchange`: the exchange this task will be published to.
27///   e.g: `#[task_exchange = "batch.example"]`
28///   **default value**: `""`
29/// * `task_routing_key`: the routing key associated to the task.
30///   e.g: `#[task_routing_key = "mailer"]`
31/// * `task_timeout`: Number of seconds available for the task to execute. If the time limit is
32///   exceeded, the task's process is killed and the task is marked as failed.
33///   e.g: `#[task_timeout = "120"]`
34///   **default value**: 900 (15 minutes)
35/// * `task_retries`: Number of times the task should be retried in case of error.
36///   e.g: `#[task_retries = "5"]`
37///   **default value**: 2
38#[proc_macro_derive(Task,
39                    attributes(task_name, task_exchange, task_routing_key, task_timeout,
40                               task_retries))]
41pub fn task_derive(input: TokenStream) -> TokenStream {
42    let input: DeriveInput = syn::parse(input).unwrap();
43    let task_name = get_derive_name_attr(&input);
44    let task_exchange = get_derive_exchange_attr(&input);
45    let task_routing_key = get_derive_routing_key_attr(&input);
46    let task_timeout = get_derive_timeout_attr(&input);
47    let task_retries = get_derive_retries_attr(&input);
48    let name = &input.ident;
49
50    let expanded = quote! {
51        impl ::batch::Task for #name {
52
53            fn name() -> &'static str {
54                #task_name
55            }
56
57            fn exchange() -> &'static str {
58                #task_exchange
59            }
60
61            fn routing_key() -> &'static str {
62                #task_routing_key
63            }
64
65            fn timeout() -> Option<::std::time::Duration> {
66                #task_timeout
67            }
68
69            fn retries() -> u32 {
70                #task_retries
71            }
72        }
73    };
74    expanded.into()
75}
76
77fn get_derive_name_attr(input: &DeriveInput) -> Tokens {
78    let attr = {
79        let raw = get_str_attr_by_name(&input.attrs, "task_name");
80        raw.unwrap_or_else(|| input.ident.as_ref().to_string())
81    };
82    attr.into_tokens()
83}
84
85fn get_derive_exchange_attr(input: &DeriveInput) -> Tokens {
86    let attr = {
87        let raw = get_str_attr_by_name(&input.attrs, "task_exchange");
88        raw.unwrap_or_else(|| "".to_string())
89    };
90    attr.into_tokens()
91}
92
93fn get_derive_routing_key_attr(input: &DeriveInput) -> Tokens {
94    let attr = {
95        let raw = get_str_attr_by_name(&input.attrs, "task_routing_key");
96        raw.expect("task_routing_key is a mandatory attribute when deriving Task")
97    };
98    attr.into_tokens()
99}
100
101fn get_derive_timeout_attr(input: &DeriveInput) -> Tokens {
102    let attr = {
103        let raw = get_str_attr_by_name(&input.attrs, "task_timeout");
104        raw.unwrap_or_else(|| "900".to_string())
105    };
106    let timeout = attr.parse::<u64>()
107        .expect("Couldn't parse timeout as an unsigned integer");
108    quote! {
109        ::std::option::Option::Some(::std::time::Duration::from_secs(#timeout))
110    }
111}
112
113fn get_derive_retries_attr(input: &DeriveInput) -> Tokens {
114    let attr = {
115        let raw = get_str_attr_by_name(&input.attrs, "task_retries");
116        raw.unwrap_or_else(|| "2".to_string())
117    };
118    let retries = attr.parse::<u32>()
119        .expect("Couldn't parse retries as an unsigned integer");
120    quote! {
121        #retries
122    }
123}
124
125/// Gets the string value of an attribute by its name.
126fn get_str_attr_by_name(haystack: &[syn::Attribute], needle: &str) -> Option<String> {
127    let attr = get_raw_attr_by_name(haystack, needle);
128    attr.and_then(|attr| {
129        if let Lit::Str(literal) = attr {
130            Some(literal.value())
131        } else {
132            None
133        }
134    })
135}
136
137/// Gets the raw value of an attribute by its name.
138fn get_raw_attr_by_name(haystack: &[syn::Attribute], needle_raw: &str) -> Option<Lit> {
139    let needle = Ident::from(needle_raw);
140    for attr in haystack {
141        let meta = match attr.interpret_meta() {
142            Some(meta) => meta,
143            None => continue,
144        };
145        let nv = match meta {
146            Meta::NameValue(nv) => nv,
147            _ => continue,
148        };
149        if nv.ident != needle {
150            continue;
151        }
152        return Some(nv.lit.clone());
153    }
154    None
155}