Skip to main content

oopsie_macros/
lib.rs

1#![cfg_attr(
2    feature = "unstable-error-generic-member-access",
3    doc(test(attr(feature(error_generic_member_access))))
4)]
5#![warn(missing_docs)]
6//! Procedural macros backing the `oopsie` error-handling crate.
7//!
8//! This crate provides the [`#[oopsie]`](macro@oopsie) attribute — the canonical
9//! way to define an error type — and the underlying [`Oopsie`](derive@Oopsie)
10//! derive. Both generate context selectors plus `Display` and `Error` impls; the
11//! attribute additionally derives `Debug` and can inject diagnostic fields such
12//! as backtraces, span traces, timestamps, and caller locations.
13//!
14//! Depend on the `oopsie` facade rather than this crate directly; the macros are
15//! re-exported from there alongside the runtime they expand against.
16
17pub(crate) mod derive;
18pub(crate) mod keyword_docs;
19mod oopsie_attr;
20pub(crate) mod traced;
21pub(crate) mod utils;
22
23/// Derive macro that generates context selectors, `Display`, and `Error` impls
24/// for a struct or enum.
25///
26/// This is the engine the [`#[oopsie]`](macro@oopsie) attribute drives, and the
27/// attribute is the canonical entry point. On top of this derive the attribute
28/// also derives `Debug` for you and, with `traced`, injects the backtrace,
29/// span-trace, and caller-location fields. Reach for the derive directly only
30/// when you want to supply those pieces yourself; otherwise prefer the attribute.
31///
32/// # What gets generated
33///
34/// For each variant or struct, the derive produces a **context selector** — a struct
35/// holding all fields except the source error and `#[oopsie(capture)]` fields.
36///
37/// - **Leaf** selectors (no source field) expose `.build()` and `.fail()`
38///   (shorthand for `Err(self.build())`).
39/// - **Source** selectors instead expose `.build_error(source)` via the
40///   `Contextual` trait — they have no `.build()`.
41///
42/// All fields accept `Into<T>`, so `"str"` is accepted for `String` fields.
43///
44/// # Usage
45///
46/// ```ignore
47/// use oopsie::ResultExt as _;
48///
49/// #[derive(Debug, oopsie::Oopsie)]
50/// #[oopsie(module(false))]
51/// pub enum MyError {
52///     #[oopsie("Connection to {host} failed")]
53///     Connect { host: String, source: std::io::Error },
54/// }
55///
56/// # fn main() {
57/// let result: Result<(), std::io::Error> = Err(std::io::Error::other("refused"));
58/// let err = result.context(Connect { host: "db.example.com" }).unwrap_err();
59/// assert_eq!(err.to_string(), "Connection to db.example.com failed");
60/// # }
61/// ```
62///
63/// See the [`oopsie`](https://docs.rs/oopsie) crate docs for the full attribute reference.
64#[proc_macro_derive(Oopsie, attributes(oopsie))]
65pub fn oopsie_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
66    match derive::expand(input.into()) {
67        Ok(tokens) => {
68            let dep = utils::manifest_dep_token();
69            quote::quote! { #tokens #dep }.into()
70        }
71        Err(err) => err.to_compile_error().into(),
72    }
73}
74
75/// Attribute macro — the primary way to define an `oopsie` error type.
76///
77/// Generates context selectors, `Display`, `Error`, and `Debug` impls in one
78/// attribute. No need for `#[derive(Debug, Oopsie)]`.
79///
80/// # Usage
81///
82/// ```ignore
83/// use oopsie::ResultExt as _;
84///
85/// #[oopsie::oopsie]
86/// pub enum MyError {
87///     #[oopsie("Connection to {host} failed")]
88///     Connect { host: String, source: std::io::Error },
89/// }
90///
91/// # fn main() {
92/// let result: Result<(), std::io::Error> = Err(std::io::Error::other("refused"));
93/// let err = result.context(my_oopsies::Connect { host: "db.example.com" }).unwrap_err();
94/// assert_eq!(err.to_string(), "Connection to db.example.com failed");
95/// # }
96/// ```
97///
98/// ## Diagnostics
99///
100/// Pass `traced` to automatically inject a backtrace field (and a span-trace when
101/// the `tracing` feature is enabled):
102///
103/// ```ignore
104/// #[oopsie::oopsie(traced)]
105/// pub enum MyError {
106///     #[oopsie("Connection failed")]
107///     Connect,
108/// }
109/// # fn main() {}
110/// ```
111///
112/// ## Parameters
113///
114/// The bare `#[oopsie]` adds no diagnostics; it is equivalent to
115/// `#[derive(Debug, Oopsie)]`. The arguments below tune what gets generated;
116/// each has its own subsection further down.
117///
118/// | Parameter | Effect |
119/// |-----------|--------|
120/// | `traced` | Inject a backtrace field (and a span-trace under the `tracing` feature), plus an auto error code |
121/// | `path` | Path to the `oopsie` crate in generated impls |
122/// | `debug` | Skip the automatic `Debug` derive |
123///
124#[doc = include_str!("../keyword_docs/attr/traced.md")]
125#[doc = include_str!("../keyword_docs/attr/path.md")]
126#[doc = include_str!("../keyword_docs/attr/debug.md")]
127///
128/// ## `traced(...)` options
129///
130/// These keys are spelled nested inside `traced(...)`. `backtrace`/`spantrace`
131/// take an optional settings block (`r#type`, `boxed`, `enabled`); `timestamp`
132/// takes `chrono`/`provide`; `packed`/`boxed` are pair-level layout flags.
133///
134/// | Option | Effect |
135/// |--------|--------|
136/// | `backtrace` | Enable/tune the captured backtrace (default on) |
137/// | `spantrace` | Enable/tune the captured span trace (default on; captures nothing without the `tracing` feature) |
138/// | `timestamp` | Inject an auto-captured timestamp field (default off) |
139/// | `location` | Inject an auto-captured call-site `Location` field (default on) |
140/// | `packed` | Store both traces as one `(Backtrace, SpanTrace)` field |
141/// | `boxed` | Box the injected trace field(s) |
142/// | `code` | Tune (or disable) the auto error code |
143/// | `chrono` | Use `chrono::DateTime<Local>` timestamps |
144/// | `provide` | Expose the timestamp via the provider API |
145/// | `r#type` | Override the injected type for this part |
146/// | `enabled` | Explicit on/off inside a settings block |
147///
148#[doc = include_str!("../keyword_docs/traced/backtrace.md")]
149#[doc = include_str!("../keyword_docs/traced/spantrace.md")]
150#[doc = include_str!("../keyword_docs/traced/timestamp.md")]
151#[doc = include_str!("../keyword_docs/traced/location.md")]
152#[doc = include_str!("../keyword_docs/traced/packed.md")]
153#[doc = include_str!("../keyword_docs/traced/boxed.md")]
154#[doc = include_str!("../keyword_docs/traced/code.md")]
155#[doc = include_str!("../keyword_docs/traced/chrono.md")]
156#[doc = include_str!("../keyword_docs/traced/provide.md")]
157#[doc = include_str!("../keyword_docs/traced/type.md")]
158#[doc = include_str!("../keyword_docs/traced/enabled.md")]
159///
160/// With `traced`, every variant (or the struct itself) also gets an automatic
161/// error code unless it is `transparent` or carries an explicit
162/// `#[oopsie(code = "...")]`. The code is `module_path::Type` for structs and
163/// `module_path::Type::Variant` for enum variants; `Report` renders it as
164/// `Error[...]:` in the report header.
165///
166/// Container-level `#[oopsie(...)]` attributes (`module`, `vis`, `size`, etc.)
167/// are placed on the type itself, not in the attribute macro's argument list.
168#[proc_macro_attribute]
169pub fn oopsie(
170    attrs: proc_macro::TokenStream,
171    element: proc_macro::TokenStream,
172) -> proc_macro::TokenStream {
173    match oopsie_attr::expand(attrs.into(), element.into()) {
174        Ok(tokens) => {
175            let dep = utils::manifest_dep_token();
176            quote::quote! { #tokens #dep }.into()
177        }
178        Err(err) => err.to_compile_error().into(),
179    }
180}