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:

[dependencies]
charon-error = "0.1"

Quick Start

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:

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:

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

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:

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:

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:

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.