Skip to main content

xaynet_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6    parse::{Parse, ParseStream, Result},
7    parse_macro_input,
8    Expr,
9    Token,
10};
11struct Send {
12    sender: Expr,
13    metrics: Vec<Expr>,
14}
15
16impl Parse for Send {
17    fn parse(input: ParseStream) -> Result<Self> {
18        // metrics!(sender, metric_1);
19        let sender = input.parse()?; // sender
20        let mut metrics = Vec::new();
21
22        // at least one metric is required, otherwise parse will fail.
23        input.parse::<Token![,]>()?; // ,
24        let metric = input.parse()?; // metric_1
25        metrics.push(metric);
26
27        // metrics!(sender, metric_1, metric_N);
28        loop {
29            if input.is_empty() {
30                break;
31            }
32
33            input.parse::<Token![,]>()?; // ,
34            let metric = input.parse()?; // metrics_N
35
36            metrics.push(metric);
37        }
38
39        Ok(Send { sender, metrics })
40    }
41}
42
43/// Allows one or multiple metrics to be sent through a `Sender` when the `metrics` feature is
44/// enabled.
45///
46/// The idea is to only include the code for sending metrics if the `metrics` feature flag is enabled
47/// during compilation. This can be achieved through conditional compilation, more precisely with the
48/// attribute `cfg`.
49///
50/// See [here](https://www.worthe-it.co.za/programming/2018/11/18/compile-time-feature-flags-in-rust.html)
51/// for more information about conditional compilation in Rust.
52///
53/// This macro helps to reduce the usage of the attribute `#[cfg(feature = "metrics")]` within the
54/// source code.
55///
56/// ## Macro arguments:
57///
58/// `metrics!(sender, metric_1, metric_2, metric_N)`
59///
60/// ## Basic usage:
61///
62/// ```ignore
63/// fn main() {
64///     metrics!(sender, metrics::round::total_number::update(1));
65///
66///     metrics!(
67///         sender,
68///         metrics::round::total_number::update(1),
69///         metrics::masks::total_number::update(1, 1, PhaseName::Idle)
70///     );
71/// }
72/// ```
73///
74/// Equivalent code not using `metrics!`
75///
76/// ```ignore
77/// fn main() {
78///     #[cfg(feature = "metrics")]
79///     {
80///         sender.send(metrics::round::total_number::update(1)),
81///     };
82///
83///     #[cfg(feature = "metrics")]
84///     {
85///         sender.send(metrics::round::total_number::update(1)),
86///         sender.send(metrics::masks::total_number::update(1, 1, PhaseName::Idle)),
87///     };
88/// }
89/// ```
90///
91/// ## Sender
92///
93/// A `Sender` must implement the method `pub fn send(&self, metrics: T)` where `T`
94/// is the type of the metric.
95///
96/// ### Example of a `Sender` implementation
97///
98/// ```ignore
99/// use influxdb::WriteQuery;
100/// use tokio::sync::mpsc::Sender;
101///
102/// pub struct MetricsSender(Sender<WriteQuery>);
103///
104/// impl MetricsSender {
105///     pub fn send(&mut self, query: WriteQuery) {
106///         let _ = self.0.try_send(query).map_err(|e| error!("{}", e));
107///     }
108/// }
109/// ```
110#[proc_macro]
111pub fn metrics(input: TokenStream) -> TokenStream {
112    let Send { sender, metrics } = parse_macro_input!(input as Send);
113
114    TokenStream::from(quote! {
115            #[cfg(feature = "metrics")]
116            {
117                #(#sender.send(#metrics);)*
118            }
119    })
120}