dterror_derive/lib.rs
1//! Derive macro for the `dterror::FromContext` trait.
2//!
3//! See the `dterror` crate for usage documentation.
4
5mod expand_enum;
6mod expand_struct;
7mod field;
8mod naming;
9mod validate;
10
11use proc_macro::TokenStream;
12use proc_macro2::TokenStream as TokenStream2;
13use quote::format_ident;
14use syn::{Data, DeriveInput};
15
16/// Derive an implementation of `dterror::FromContext` for an error type.
17///
18/// For a struct, the macro generates:
19///
20/// - a `{Error}Ctx<'ctx>` struct holding every field not marked with `#[source]`, `#[from]`, or
21/// `#[location]`, deriving `Clone`, `Debug`, and `PartialEq`;
22/// - a `pub fn new(...)` constructor on `{Error}Ctx` taking one argument per context field, in
23/// declaration order;
24/// - an `impl ::dterror::FromContext for {Error}` whose `from_context` moves the context fields
25/// into the error and assigns `location` and `source` to the marked fields, when present.
26///
27/// For an enum, the macro generates a `{Error}Ctx<'ctx>` **enum** instead: one same-named
28/// variant per *constructible* variant of the error (a variant with at least one context field
29/// AND a `#[source]`/`#[from]` field), plus one public associated constructor on `{Error}Ctx` per
30/// Ctx variant, named after the variant in `snake_case`. Unit variants, marker-only
31/// variants, and source-less variants get no Ctx variant and no constructor.
32///
33/// # Attribute helpers
34///
35/// - `#[source]` or `#[from]`: the field that stores the source of the error. At most one per
36/// struct or variant.
37/// - `#[location]`: the field that stores the caller's [`std::panic::Location`]. At most one
38/// per struct or variant.
39/// - `#[context(borrow = TargetType)]`: store a borrowed reference (`&'ctx TargetType`) in Ctx
40/// instead of an owned value, deferring allocation to `from_context`. This enables zero-cost
41/// context construction when the caller already holds a reference — no eager cloning occurs on
42/// the Ok path. See `FromContext::Ctx` in the `dterror` crate for details. At most one
43/// `#[context(...)]` attribute per field, and it cannot be combined with `#[source]`/`#[from]`
44/// or `#[location]`.
45/// - `#[context(constructor = "name")]`: variant-level; renames the generated constructor for
46/// that variant to `name`, a quoted string that parses as an identifier. At most one per
47/// variant.
48///
49/// Every other field becomes a field of the generated `{Error}Ctx`. A struct with no context
50/// fields, or an enum with no constructible variant, fails to compile, as do duplicate or
51/// conflicting markers and duplicate constructor names.
52#[proc_macro_derive(FromContext, attributes(context, location, source, from))]
53pub fn derive_from_context(input: TokenStream) -> TokenStream {
54 let input = syn::parse_macro_input!(input as DeriveInput);
55 expand(&input)
56 .unwrap_or_else(syn::Error::into_compile_error)
57 .into()
58}
59
60fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
61 let ctx_ident = format_ident!("{}Ctx", input.ident);
62
63 match &input.data {
64 Data::Struct(data) => expand_struct::expand_struct_item(input, data, &ctx_ident),
65 Data::Enum(data) => expand_enum::expand_enum_item(input, data, &ctx_ident),
66 Data::Union(data) => Err(syn::Error::new_spanned(
67 data.union_token,
68 "`#[derive(FromContext)]` does not support unions",
69 )),
70 }
71}