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}