thistrace 0.1.0

Callsite provenance (file/line/col) for thiserror #[from] conversions via #[track_caller]
Documentation
# thistrace

`thistrace` adds **callsite provenance** (file/line/column) to `thiserror` enums **without** requiring `map_err(...)` at each callsite.

It works by generating `#[track_caller]` `From<T>` impls for `#[from]` conversions, so `?` captures the location where the conversion happened.

## What it solves

With plain `thiserror`, you often end up with an error chain but not *where your code wrapped/bubbled* the error.

`thistrace` captures a small “provenance frame” at each `#[from]` conversion boundary.

## Usage

Add dependencies:

```toml
[dependencies]
thistrace = "0.1"
thiserror = "2"
```

In larger codebases, you can keep `thistrace` usage mostly confined to your error module via:

```rust
use thistrace::prelude::*;
```

Annotate your `thiserror` enum with `#[traceable]` (or `#[thistrace::traceable]`):

```rust
use std::io;
use thistrace::prelude::*;

#[traceable]
#[derive(thiserror::Error, Debug)]
enum AppError {
    #[error("io")]
    Io(#[from] io::Error),
}

fn inner() -> Result<(), io::Error> {
    Err(io::Error::new(io::ErrorKind::Other, "boom"))
}

fn outer() -> Result<(), AppError> {
    inner()?; // provenance captured here
    Ok(())
}
```

Print with provenance:

```rust
let err = outer().unwrap_err();
eprintln!("{}", thistrace::DisplayTrace::new(&err));
```

### Capturing the initiation (origin) location

By default, `#[traceable]` captures the location where a `#[from]` conversion happened (the `?` site).

If you also want the location where an error was *initiated*, wrap it with `thistrace::origin(...)`
and use `thistrace::Origin<_>` as the `#[from]` source type:

```rust
use std::io;
use thistrace::{origin, traceable};

#[traceable]
#[derive(thiserror::Error, Debug)]
enum AppError {
    #[error("io")]
    Io(#[from] thistrace::Origin<io::Error>),
}

fn inner() -> Result<(), thistrace::Origin<io::Error>> {
    Err(origin(io::Error::new(io::ErrorKind::Other, "boom")))
}

fn outer() -> Result<(), AppError> {
    inner()?; // bubble frame
    Ok(())
}
```

The resulting trace includes **both** the origin frame and the bubble frame.

### Logging: `Display` vs `Debug`

- `Display` (`{}` / `%err`) prints the `thiserror` message only.
- `Debug` (`{:?}` / `?err`) will include the raw `trace` field for rewritten `#[from]` variants
  (because `#[traceable]` turns them into struct-like variants `{ source, trace }`), but it is
  not formatted as a pretty trace.
- `thistrace::DisplayTrace` is the recommended way to include a readable `at file:line:col` trace
  in logs.
- `thistrace::OneLineTrace` provides a single-line string that includes the trace and cause chain
  (useful when your log system treats newlines poorly).

### Logging: structured frames (Splunk-friendly)

If you log to Splunk/ELK/etc, prefer **structured fields** (often via `tracing` + JSON output)
and log the frames as a field:

```rust
use thistrace::{HasTrace, trace_frames};

tracing::error!(
    error = %err,
    trace = ?trace_frames(&err),
    "request failed"
);
```

### Logging: common `tracing` patterns

One-line error string (good default):

```rust
tracing::error!(
    error = %thistrace::OneLineTrace::new(&err),
    "request failed"
);
```

Human-readable multi-line trace (nice in dev logs):

```rust
tracing::error!(
    error = %thistrace::DisplayTrace::new(&err),
    "request failed"
);
```

Example output:

```text
io
  at src/config.rs:42:13
  at src/main.rs:18:5

caused by: No such file or directory (os error 2)
```

Both structured frames and one-line text:

```rust
tracing::error!(
    error = %err,
    trace = ?thistrace::trace_frames(&err),
    error_trace = %thistrace::OneLineTrace::new(&err),
    "request failed"
);
```

### Capturing a bubble frame without `core::ops::function` noise

If you want to append a “bubble” frame using `map_err`, prefer a closure (or the helper macro)
instead of passing `thistrace::bubble` directly. This ensures the captured location is *your*
callsite:

```rust
// Good: captures this line in your code
result.map_err(thistrace::bubble_err!())?;

// Also good (equivalent)
result.map_err(|e| thistrace::bubble(e))?;

// Avoid: can capture inside core::ops::function.rs
// result.map_err(thistrace::bubble)?; // or bubble_any
```

If you want to bubble *and* convert back into your own error type in one step, use:

```rust
result.map_err(thistrace::bubble_into!(MyError))?;
```

If you're already returning `Result<_, thistrace::Bubbled<MyError>>`, use `rebubble_err!()` to
append another frame without producing `Bubbled<Bubbled<_>>`:

```rust
result.map_err(thistrace::rebubble_err!())?;
```

### Filling non-`Default` context fields (callsite helper)

If your error variant has extra fields that you want to fill at the callsite (instead of using
`Default::default()`), use `from_with_trace!`:

```rust
from_with_trace!(
    some_fallible_call(),
    MyError::Io { path: my_path.clone() }
)?;
```

To set a field to `Default::default()` explicitly, use `_`:

```rust
from_with_trace!(
    some_fallible_call(),
    MyError::Io { path: _ }
)?;
```

## Limitations

- **Only captures at conversion boundaries**: If you `?` the *same* error type upward (no `From` conversion), there is no stable way to auto-capture without an explicit wrapper step.
- **Limited `#[from]` shapes**: supports tuple variants with exactly one `#[from]` field (e.g. `Io(#[from] io::Error)`), tuple variants with extra context fields (e.g. `Io(#[from] io::Error, PathBuf)`), and struct variants with a single `#[from]` field (e.g. `Io { #[from] source: io::Error, path: PathBuf }`). Extra fields are initialized with `Default::default()`.
- **Generics**: supported for `#[traceable]` enums, including `where` clauses.
- **Non-`Default` context fields**: use `from_with_trace!(...)` to fill fields at the callsite while still capturing a trace frame. You can also use `_` to set a field to `Default::default()`.

## License

Licensed under either of

- Apache License, Version 2.0 (`LICENSE-APACHE`)
- MIT license (`LICENSE-MIT`)

at your option.