context_attribute/
lib.rs

1//!  Set the error [`context`] using doc comments.
2//!
3//! This is useful because instead of writing manual error messages to provide context to an error, it
4//! automatically derives it from doc comments. This works especially well for async contexts, where
5//! stack traces may not be persisted past yield points and thread boundaries. But contexts do.
6//!
7//! [`context`]: https://docs.rs/failure/0.1.5/failure/trait.ResultExt.html#tymethod.context
8//!
9//! ## Examples
10//!
11//! ```rust
12//! use context_attribute::context;
13//! use failure::{ensure, ResultExt};
14//!
15//! /// Square a number if it's less than 10.
16//! #[context]
17//! fn square(num: usize) -> Result<usize, failure::Error> {
18//!     ensure!(num < 10, "Number was too large");
19//!     Ok(num * num)
20//! }
21//!
22//! fn main() -> Result<(), failure::Error> {
23//!     let args = std::env::args();
24//!     ensure!(args.len() == 2, "usage: square <num>");
25//!     let input = args.skip(1).next().unwrap().parse()?;
26//!
27//!     println!("result is {}", square(input)?);
28//!
29//!     Ok(())
30//! }
31//! ```
32//!
33//! ```sh
34//! $ cargo run --example square 12
35//! Error: ErrorMessage { msg: "Number was too large" }
36//! Square a number if it's less than 10.
37//! ```
38
39#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
40#![deny(missing_debug_implementations, nonstandard_style)]
41#![warn(missing_docs, missing_doc_code_examples)]
42#![cfg_attr(test, deny(warnings))]
43#![recursion_limit = "512"]
44
45extern crate proc_macro;
46
47use proc_macro::TokenStream;
48use quote::{quote, quote_spanned};
49use syn::spanned::Spanned;
50
51/// Use a doc comment to annotate the failure context of a function or try
52/// block.
53///
54/// # Examples
55///
56/// ```
57/// use context_attribute::context;
58/// use failure::{ensure, ResultExt};
59///
60/// fn main() -> Result<(), failure::Error> {
61///     let _ = square(2)?;
62///     let _ = square(5)?;
63///     let _ = square(11)?;
64/// }
65///
66/// /// Square a number if it's less than 10.
67/// #[context]
68/// fn square(num: usize) -> Result<String, >{
69///     ensure!(num < 10, "Number was larger than 10");
70///     num * num
71/// }
72/// ```
73#[proc_macro_attribute]
74pub fn context(_attr: TokenStream, item: TokenStream) -> TokenStream {
75    let input = syn::parse_macro_input!(item as syn::ItemFn);
76
77    let attrs = &input.attrs;
78    let doc = attrs.iter().find(|attr| format!("{}", attr.path.segments.first().unwrap().value().ident) == "doc");
79    let doc = match doc {
80        Some(doc) => {
81            let mut iter = doc.clone().tts.into_iter().skip(1);
82            iter.next().unwrap()
83        },
84        None => return TokenStream::from(quote_spanned! {
85            input.span() => compile_error!("no doc comment provided")
86        }),
87    };
88
89
90    let vis = &input.vis;
91    let constness = &input.constness;
92    let unsafety = &input.unsafety;
93    let asyncness = &input.asyncness;
94    let abi = &input.abi;
95
96    let generics = &input.decl.generics;
97    let name = &input.ident;
98    let inputs = &input.decl.inputs;
99    let output = &input.decl.output;
100    let body = &input.block.stmts;
101
102    let args: Vec<syn::Pat> = inputs.pairs().filter_map(|pair| {
103        match pair.into_value() {
104            syn::FnArg::Captured(arg) => Some(arg.pat.clone()),
105            _ => return None,
106        }
107    }).collect();
108
109    let result = quote! {
110        #(#attrs)*
111        #vis #constness #unsafety #asyncness #abi fn #generics #name(#(#inputs)*) #output {
112            #constness #unsafety #asyncness #abi fn #generics #name(#(#inputs)*) #output {
113                #(#body)*
114            }
115            Ok(#name(#(#args)*).context(#doc.trim())?)
116        }
117    };
118
119    result.into()
120}