Skip to main content

easy_macros_always_context/
lib.rs

1mod context_gen;
2mod search;
3mod settings;
4use settings::Settings;
5
6use helpers::find_crate_list;
7use proc_macro::TokenStream;
8use quote::{ToTokens, quote};
9use search::item_handle;
10
11fn crate_missing_panic(crate_name: &str, for_macro: &str) -> ! {
12    panic!(
13        "Using {for_macro} requires `{crate_name}` (or `easy-macros` crate) to be present in dependencies! You can add it with `{crate_name} = \"*\"` in your Cargo.toml dependencies or with `cargo add {crate_name}` command."
14    );
15}
16
17fn context_crate() -> proc_macro2::TokenStream {
18    if let Some(found) = find_crate_list(&[
19        ("easy-macros", quote! {}),
20        ("easy-macros-helpers", quote! {}),
21    ]) {
22        found
23    } else {
24        crate_missing_panic("easy-macros-helpers", "always_context");
25    }
26}
27
28#[proc_macro_attribute]
29/// Automatically adds `.with_context(context!())` to all `?` operators that don't already have context.
30///
31/// Transforms `operation()?` into `operation().with_context(context!("operation()"))?`
32/// with function call details, arguments, and file location.
33///
34/// # Requirements
35///
36/// - Function must return `anyhow::Result<T>` or `Result<T, UserFriendlyError>` (please add an issue if you need support for other types)
37///
38/// # Control Attributes
39///
40/// ## Macro-level
41/// - Skip
42///     - `easy_sql` or `es` - Skips requiring context for query! macros
43///     - `skip_macros` or `!` - Skips requiring context for macros entirely
44///      
45/// Examples: `#[always_context(skip(!))]`, `#[always_context(skip(macros, easy_sql))]`, `#[always_context(skip(!, es))]`
46///
47/// ## Function-level
48/// - `#[no_context]` - Disable context generation entirely
49/// - `#[no_context_inputs]` - Add context but exclude function arguments  
50/// - `#[enable_context]` - Re-enable context (useful in macros where auto-disabled)
51///
52/// ## Argument-level
53/// - `#[context(display)]` - Use `Display` instead of `Debug` for formatting
54/// - `#[context(.method())]` - Call method on argument before displaying
55/// - `#[context(tokens)]` - Format as token stream (equivalent to `display` + `.to_token_stream()`)
56/// - `#[context(tokens_vec)]` - Format as token stream collection
57/// - `#[context(ignore)]` or `#[context(ignored)]` or `#[context(no)]` - Exclude this argument from context
58///
59/// # Limitations
60///
61/// These expressions before `?` require manual `.with_context()` or `.context()`:
62/// blocks, control flow (`if`/`match`/`while`/`for`/`loop`), field access, macros.
63pub fn always_context(attr: TokenStream, item: TokenStream) -> TokenStream {
64    let mut parsed = syn::parse_macro_input!(item as syn::Item);
65
66    let settings = syn::parse_macro_input!(attr as Settings);
67
68    item_handle(&mut parsed, settings);
69
70    parsed.into_token_stream().into()
71}
72#[proc_macro_attribute]
73/// Debug version of `always_context` that panics with the result.
74#[doc(hidden)]
75pub fn always_context_debug(attr: TokenStream, item: TokenStream) -> TokenStream {
76    let mut parsed = syn::parse_macro_input!(item as syn::Item);
77
78    let settings = syn::parse_macro_input!(attr as Settings);
79
80    item_handle(&mut parsed, settings);
81
82    let debug_tokens = parsed.into_token_stream().to_string();
83    let error_tokens = quote! {
84        compile_error!(#debug_tokens);
85    };
86    error_tokens.into()
87}