charon-error 0.1.0

Structured error and panic handling with rich reports and bug reporting functionality
Documentation
# charon-error

Structured error handling for Rust with rich error reports, data sensitivity labels, panic capture, and GitLab issue integration.

Named after **Charon**, the Greek ferryman of the dead across the river Styx. This crate ferries your program from a running state to a graceful death, collecting every detail along the way.

## Features

- **Error chains** — Stack multiple errors with `ErrorReport` to preserve full context from root cause to top-level failure
- **Sensitivity labels** — Tag data as `Public`, `Private`, `Internal`, or `Confidential` to control what information gets shared in reports
- **Panic hooks** — Capture panics and display human-readable error messages with `setup_panic!` and `setup_panic_simple!`
- **Issue submission** — Generate pre-filled GitLab issue URLs directly from errors so users can report bugs with one click
- **Tracing integration** — Works with the `tracing` crate. Use `#[instrument]` and span traces are captured automatically in error frames
- **Flexible formatting** — Configure output detail level (Compact, Medium, Full, Debug) and indentation style

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
charon-error = "0.1"
```

## Quick Start

```rust
use charon_error::prelude::*;

#[derive(Debug, ThisError)]
enum AppError {
    #[error("failed to load configuration")]
    ConfigLoad,
    #[error("failed to start server")]
    ServerStart,
}

#[instrument]
fn load_config() -> ResultER<String> {
    std::fs::read_to_string("config.toml")
        .change_context(AppError::ConfigLoad)
}

#[instrument]
fn start_server() -> ResultER<()> {
    let _config = load_config()
        .change_context(AppError::ServerStart)?;
    Ok(())
}
```

`ResultER<T>` is a type alias for `Result<T, ErrorReport>`. The `ResultExt` trait (imported via the prelude) adds `.change_context()`, `.error_attach()`, and `.unwrap_error()` to any `Result` or `Option`.

## Error Context and Attachments

### Adding Context

Use `.change_context()` to wrap errors with higher-level meaning:

```rust
use charon_error::prelude::*;

#[instrument]
fn read_user(id: u64) -> ResultER<String> {
    std::fs::read_to_string(format!("users/{id}.json"))
        .change_context(StringError::new("failed to read user"))
}
```

### Attaching Data

Attach diagnostic data to errors with sensitivity labels:

```rust
use charon_error::prelude::*;

#[instrument]
fn process_order(order_id: &str) -> ResultER<()> {
    do_work()
        .error_attach_public_string("order_id", order_id.to_owned())?;
    Ok(())
}

fn do_work() -> ResultER<()> { Ok(()) }
```

## Sensitivity Labels

`ErrorSensitivityLabel` controls who can see attached data:

| Level                | Data Owner | Other Users | Internal | Encrypted |
|----------------------|:----------:|:-----------:|:--------:|:---------:|
| `Public`             |    yes     |     yes     |   yes    |    no     |
| `Private`            |    yes     |      no     |   yes    |    no     |
| `PrivateConfidential`|    yes     |      no     |    no    |   yes     |
| `Internal`           |     no     |      no     |   yes    |    no     |
| `Confidential`       |     no     |      no     |   yes    |   yes     |
| `HighlyConfidential` |     no     |      no     |   yes    |   yes     |

Note: Encryption is not implemented yet.

## Panic Hooks

### Full Panic Hook with Issue Submission

```rust
use charon_error::prelude::*;
use charon_error::prelude::gitlab_er::*;

#[allow(dead_code)]
fn check_if_common_errors(error_message: &str) -> Option<String> {
    // use colored::*;
    match error_message {
        "Address/Port binding error." => {
            Some("We got a Address/Port binding error.\n\
            You most likely already have the application already open somewhere. \
            Because the port is already in use. If this is not the case an other app \
            is using this port. You can change the port in the config file.\n\
            Change the port `config.json` -> server -> port.\n\
            Change the value to 20001 and try again.".to_owned())
        },
        _ => {
            None
        },
    }
}

fn main() -> ResultER<()> {
    // Configure GitLab integration
    GitLabErrorReport::setup_global_config(GitLabERGlobalSettings {
        domain: "gitlab.com".to_owned(),
        project_path: "my-group/my-project".to_owned(),
        labels: vec!["bug".to_owned(), "auto-generated".to_owned()],
        url_char_limit: 6000,
        ..Default::default()
    })
    .unwrap_error();

    // Install panic hook
    setup_panic!(GitLabErrorReport, check_if_common_errors);

    // Your application code here
    Ok(())
}
```

When a panic occurs:
- **Known errors**: Displays a friendly message from `check_if_common_errors`
- **Unknown errors (debug build)**: Shows the full error report with a clickable link to create a GitLab issue
- **Unknown errors (release build)**: Shows a user-friendly message with report instructions

### Simple Panic Hook

For applications that do not need issue submission:

```rust
use charon_error::prelude::*;

fn main() {
    setup_panic_simple!();

    // Your application code here
}
```

## Tracing Integration

`charon-error` integrates with the `tracing` crate. Annotate functions with `#[instrument]` and span traces are automatically captured in each error frame:

```rust
use charon_error::prelude::*;

#[instrument]
fn process_request(user_id: u64) -> ResultER<()> {
    validate_user(user_id)?;
    Ok(())
}

#[instrument]
fn validate_user(id: u64) -> ResultER<()> {
    Err(StringError::new("user not found"))?
}
```

The error report will include the span trace showing the call chain through `process_request` and `validate_user`.

## Error Formatting

Control output detail with `ErrorFmtSettings`:

```rust
use charon_error::{ErrorFmt, ErrorFmtSettings, ErrorFmtLoD, ErrorReport, StringError};

let report = ErrorReport::from_error(StringError::new("example"));

// Compact output
let compact = report.stringify(ErrorFmtSettings {
    level_of_detail: ErrorFmtLoD::Compact,
    ..Default::default()
});

// Full output with no color (for logs)
let full = report.stringify(ErrorFmtSettings {
    level_of_detail: ErrorFmtLoD::Full,
    enable_color: false,
    ..Default::default()
});
```

Detail levels:
- **Compact** — Key fields only
- **Medium** — Balanced (default)
- **SubmitReport** — Tailored for issue submission (no color)
- **Full** — All available information
- **Debug** — Structural output for debugging the error system itself

## GitLab Configuration

`GitLabERGlobalSettings` fields:

| Field                  | Default         | Description                              |
|------------------------|-----------------|------------------------------------------|
| `domain`               | `"gitlab.com"`  | GitLab instance domain                   |
| `project_path`         | `"<not set>"`   | Group/project path (e.g. `"org/project"`)  |
| `labels`               | `["Auto Generated Issue"]` | Labels applied to created issues |
| `url_char_limit`       | `2085`          | Max URL length before trimming report    |
| `title_char_limit`     | `1024`          | Max issue title length                   |
| `description_char_limit`| `1_048_576`    | Max issue description length (1MB)       |

## Roadmap

- [ ] GitHub issue submission support
- [ ] Encryption for `Confidential` and `HighlyConfidential` data
- [ ] Configurable global formatting settings

## Use of AI in this project

This project was hand written before AI. It will be using AI to assist in development.
However ALL code is manually reviewed and improve.

---

## License

The code in this project is licensed under the MIT or Apache 2.0 license.

All contributions, code and documentation, to this project will be similarly licensed.