zipkin_macros/
lib.rs

1//  Copyright 2020 Palantir Technologies, Inc.
2//
3//  Licensed under the Apache License, Version 2.0 (the "License");
4//  you may not use this file except in compliance with the License.
5//  You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14//! Macros for use with `zipkin`.
15//!
16//! You should not depend on this crate directly.
17extern crate proc_macro;
18
19use proc_macro::TokenStream;
20use proc_macro2::Span;
21use quote::{quote, ToTokens};
22use syn::parse::{Parse, ParseStream};
23use syn::punctuated::Punctuated;
24use syn::{parse_macro_input, Error, Expr, ImplItemFn, Lit, LitStr, Meta, Stmt, Token};
25
26/// Wraps the execution of a function or method in a span.
27///
28/// Both normal and `async` methods and functions are supported. The name of the span is specified as an argument
29/// to the macro attribute.
30///
31/// Requires the `macros` Cargo feature.
32///
33/// # Examples
34///
35/// ```ignore
36/// #[zipkin::spanned(name = "shave yaks")]
37/// fn shave_some_yaks(yaks: &mut [Yak]) {
38///     // ...
39/// }
40///
41/// #[zipkin::spanned(name = "asynchronously shave yaks")]
42/// async fn shave_some_other_yaks(yaks: &mut [Yak]) {
43///     // ...
44/// }
45///
46/// struct Yak;
47///
48/// impl Yak {
49///     #[zipkin::spanned(name = "shave a yak")]
50///     fn shave(&mut self) {
51///         // ...
52///     }
53///
54///     #[zipkin::spanned(name = "asynchronously shave a yak")]
55///     async fn shave_nonblocking(&mut self) {
56///          // ...
57///     }
58/// }
59/// ```
60#[proc_macro_attribute]
61pub fn spanned(args: TokenStream, item: TokenStream) -> TokenStream {
62    let options = parse_macro_input!(args as Options);
63    let func = parse_macro_input!(item as ImplItemFn);
64
65    spanned_impl(options, func).unwrap_or_else(|e| e.to_compile_error().into())
66}
67
68fn spanned_impl(options: Options, mut func: ImplItemFn) -> Result<TokenStream, Error> {
69    let name = &options.name;
70
71    if func.sig.asyncness.is_some() {
72        let stmts = &func.block.stmts;
73        func.block.stmts = vec![
74            syn::parse2(quote! {
75                let __macro_impl_span = zipkin::next_span()
76                    .with_name(#name)
77                    .detach();
78            })
79            .unwrap(),
80            Stmt::Expr(
81                syn::parse2(quote! {
82                    __macro_impl_span.bind(async move { #(#stmts)* }).await
83                })
84                .unwrap(),
85                None,
86            ),
87        ];
88    } else {
89        let stmt = quote! {
90            let __macro_impl_span = zipkin::next_span().with_name(#name);
91        };
92        func.block.stmts.insert(0, syn::parse2(stmt).unwrap());
93    };
94
95    Ok(func.into_token_stream().into())
96}
97
98struct Options {
99    name: LitStr,
100}
101
102impl Parse for Options {
103    fn parse(input: ParseStream) -> syn::Result<Self> {
104        let args = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
105
106        let mut name = None;
107
108        for arg in args {
109            let meta = match arg {
110                Meta::NameValue(meta) => meta,
111                _ => return Err(Error::new_spanned(&arg, "invalid attribute syntax")),
112            };
113
114            if meta.path.is_ident("name") {
115                match meta.value {
116                    Expr::Lit(lit) => match lit.lit {
117                        Lit::Str(lit) => name = Some(lit),
118                        lit => return Err(Error::new_spanned(&lit, "expected a string literal")),
119                    },
120                    _ => return Err(Error::new_spanned(meta, "expected `name = \"...\"`")),
121                }
122            } else {
123                return Err(Error::new_spanned(meta.path, "unknown option"));
124            }
125        }
126
127        Ok(Options {
128            name: name.ok_or_else(|| Error::new(Span::call_site(), "missing `name` option"))?,
129        })
130    }
131}