charon-error 0.1.0

Structured error and panic handling with rich reports and bug reporting functionality
Documentation
# AI Agent Usage Guide for charon-error

This document helps AI coding assistants understand how to use the `charon-error` crate correctly when writing Rust code.

## When to Use This Crate

Use `charon-error` when the project needs:
- Structured error chains with context (similar to `anyhow` but with more features)
- Data sensitivity classification on error data
- Panic hooks that generate user-facing reports or issue submission URLs
- Integration with `tracing` spans in error output

## Core Pattern

The standard pattern for using `charon-error` in a project:

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

// 1. Define application errors using thiserror (re-exported as ThisError)
#[derive(Debug, ThisError)]
enum AppError {
    #[error("failed to initialize application")]
    Init,
    #[error("failed to read config")]
    ConfigRead,
}

// 2. Use ResultER<T> as the return type and chain context with change_context
#[instrument]
fn init_app(config_path: &str) -> ResultER<String> {
    let config = read_config(config_path)
        .change_context(AppError::Init)
        .error_attach_public_string("config_path", config_path.to_owned())?;
    Ok(config)
}

// 3. Wrap standard library errors at the boundary
#[instrument]
fn read_config(path: &str) -> ResultER<String> {
    std::fs::read_to_string(path)
        .change_context(AppError::ConfigRead)
}
```

## Key Types and Imports

### Prelude Import

Always start with:
```rust
use charon_error::prelude::*;
```

This imports: `ErrorReport`, `ResultER`, `ResultExt`, `StringError`, `setup_panic`, `ErrorAttachment`, `ErrorSensitivityLabel`, `ThisError` (re-export of `thiserror::Error`), and `instrument` (re-export of `tracing::instrument`).

For GitLab integration, add:
```rust
use charon_error::prelude::gitlab_er::*;
```

### Type Mapping

| What you need | Type to use |
|---|---|
| Return type for fallible functions | `ResultER<T>` |
| Wrap any `Result` error with context | `.change_context(MyError::Variant)` |
| Quick error from a string | `StringError::new("message")` |
| Attach public data to error | `.error_attach_public_string("key", value)` |
| Attach sensitive data | `.error_attach("key", ErrorSensitivityLabel::Confidential(...))` |
| Unwrap with rich panic | `.unwrap_error()` |
| Unwrap with custom message | `.expect_error("message")` |

## Common Patterns

### Pattern 1: Wrapping std/library errors

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

#[instrument]
fn read_file(path: &str) -> ResultER<String> {
    std::fs::read_to_string(path)
        .change_context(StringError::new(format!("failed to read {path}")))
}
```

### Pattern 2: Custom error types with thiserror

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

#[derive(Debug, ThisError)]
enum ConfigError {
    #[error("missing required field: {0}")]
    MissingField(String),
    #[error("invalid value for {field}: {reason}")]
    InvalidValue { field: String, reason: String },
}

#[instrument]
fn validate_config(config: &str) -> ResultER<()> {
    if config.is_empty() {
        return Err(ConfigError::MissingField("name".into()))?;
    }
    Ok(())
}
```

### Pattern 3: Attaching diagnostic data

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

#[instrument(skip(user_id))]
fn process_request(request_id: &str, user_id: u64) -> ResultER<()> {
    do_work()
        .error_attach_public_string("request_id", request_id.to_owned())
        .error_attach(
            "user_id",
            ErrorSensitivityLabel::Private(
                ErrorAttachment::String(user_id.to_string())
            ),
        )?;
    Ok(())
}

#[instrument]
fn do_work() -> ResultER<()> { Ok(()) }
```

### Pattern 4: Setting up panic hooks (in main)

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

#[instrument]
fn check_if_common_errors(msg: &str) -> Option<String> {
    None // Return Some("message") for known errors
}

#[instrument]
fn main() -> ResultER<()> {
    GitLabErrorReport::setup_global_config(GitLabERGlobalSettings {
        domain: "gitlab.com".to_owned(),
        project_path: "org/project".to_owned(),
        ..Default::default()
    }).unwrap_error();

    setup_panic!(GitLabErrorReport, check_if_common_errors);

    // Application code
    Ok(())
}
```

### Pattern 5: Using `#[instrument]` with tracing

**`#[instrument]` is critical for useful error reports.** Without it, span traces in error frames will be empty and you lose the call chain context. Add it to most functions in the main application flow.

The `instrument` macro is re-exported via the prelude (`use charon_error::prelude::*;`).

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

// Basic: captures function name and all arguments in the span
#[instrument]
fn load_data(path: &str) -> ResultER<String> {
    parse_data(&std::fs::read_to_string(path)
        .change_context(StringError::new("read failed"))?)
}

// Named: use for methods to include the type name in the span
#[instrument(name = "DataStore::save")]
fn save(key: &str, value: &str) -> ResultER<()> {
    std::fs::write(format!("/data/{key}"), value)
        .change_context(StringError::new("write failed"))
}

// Skip: exclude large or sensitive arguments from the span
#[instrument(skip(contents))]
fn parse_data(contents: &str) -> ResultER<String> {
    contents.lines().next()
        .map(|s| s.to_owned())
        .change_context(StringError::new("empty file"))
}
```

**When to use which variant:**

| Variant | Use case |
|---|---|
| `#[instrument]` | Default for most functions |
| `#[instrument(name = "Type::method")]` | `impl` methods — gives readable span names |
| `#[instrument(skip(arg))]` | Arguments that are large, sensitive, or don't implement `Debug` |
| `#[instrument(skip_all)]` | When no arguments should be recorded |

## Sensitivity Label Selection Guide

Choose the right label based on what data you are attaching:

| Data type | Recommended label |
|---|---|
| Error messages, status codes, public IDs | `Public` |
| User email, username, user-specific config | `Private` |
| Auth tokens, passwords, PII | `PrivateConfidential` |
| Internal server paths, internal IDs | `Internal` |
| API keys, database credentials | `Confidential` |
| Encryption keys, secrets | `HighlyConfidential` |

Sensitive data should be avoided being logged, but if needed to correctly debug an application it might be needed.

## Implementing SubmitErrorReport for Other Platforms

To add support for a new platform (e.g. GitHub), implement the `SubmitErrorReport` trait:

```rust
use charon_error::{ErrorReport, SubmitErrorReport, ResultER};
use url::Url;

struct GitHubErrorReport<'a> {
    error_report: &'a ErrorReport,
}

impl<'a> SubmitErrorReport<'a> for GitHubErrorReport<'a> {
    fn new(error: &'a ErrorReport) -> Self {
        GitHubErrorReport { error_report: error }
    }

    fn get_error_report(&self) -> &ErrorReport {
        self.error_report
    }

    fn get_title(&self) -> String {
        self.error_report.get_last_error_title()
    }

    fn create_message(&self) -> ResultER<String> {
        // Format the user-facing message
        todo!()
    }

    fn create_bug_report(&self) -> ResultER<String> {
        // Format the full bug report body
        todo!()
    }

    fn create_submit_url(&self) -> ResultER<Url> {
        // Create GitHub issue URL: https://github.com/org/repo/issues/new?title=...&body=...
        todo!()
    }

    fn create_submit_url_limited(&self, max_length: usize) -> ResultER<Url> {
        // Same but progressively remove detail to stay under max_length
        todo!()
    }

    fn check_existing_reports(&self) -> ResultER<Url> {
        // Create GitHub search URL
        todo!()
    }
}
```

## Error Formatting Pipeline

The formatting system uses an intermediate representation:

1. `ErrorReport` implements `ErrorFmt` trait
2. `create_format_obj()` builds an `ErrorFormatObj` tree (JSON-like structure)
3. `stringify()` converts the tree to a formatted string
4. `ErrorFmtSettings` controls detail level, color, indentation, and link format

To format errors manually:

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

// Use the stringify method with custom settings
let output = error_report.stringify(ErrorFmtSettings {
    level_of_detail: ErrorFmtLoD::Full,
    enable_color: false,
    ..Default::default()
});
```

## Common Mistakes to Avoid

1. **Do not call `setup_global_config` more than once** — It uses `OnceLock` and will return an error on the second call
2. **Do not use `unwrap()` on `ResultER`** — Use `.unwrap_error()` instead, which produces a rich error report on panic
3. **Do not forget `#[instrument]` and tracing setup** — Without `#[instrument]` on functions, span traces will be empty and error reports lose their call chain. Add it to all main-flow functions. Also requires a `tracing-subscriber` in your main function
4. **Do not attach sensitive data with `Public` label** — Always use `Confidential` or higher for secrets, tokens, and credentials