orion-error 0.7.1

Struct Error for Large Project
Documentation

orion-error

English | 简体中文

Structured error governance for large Rust codebases.

orion-error is not just an error type library.

It is a governance framework for large Rust services and multi-layer systems. It helps teams move from ad-hoc strings and mixed local conventions to one shared error model for:

  • semantic modeling
  • runtime propagation
  • context attachment
  • cross-layer conversion
  • boundary-facing output for HTTP / RPC / CLI / logs

Core building blocks:

  • stable business identities via #[derive(OrionError)]
  • one runtime carrier: StructError<R>
  • explicit first-entry conversion with into_as(...)
  • explicit cross-layer wrapping with wrap_as(...)
  • report, snapshot, and exposure helpers for service boundaries

CI Coverage Status crates.io

Why It Is Useful

Use this crate when you want:

  • one shared error language across service / repo / adapter / protocol layers
  • clear business error enums instead of scattered strings
  • one consistent way to attach detail, source, and operation context
  • stable machine-facing identity for HTTP / RPC / log / CLI boundaries
  • controlled bridging to std::error::Error only where needed
  • a system that scales better than local Result<T, String> habits

If you only need a tiny local enum inside one module, thiserror alone may be enough. If your service has layers, external boundaries, and structured error output, orion-error is the better fit.

In short:

  • thiserror is a good local modeling tool
  • orion-error is for project-wide error governance

Install

[dependencies]
orion-error = "0.7"

Default features include derive and log.

Common optional features:

[dependencies]
orion-error = { version = "0.7", features = ["serde"] }
orion-error = { version = "0.7", features = ["serde_json"] }
orion-error = { version = "0.7", features = ["tracing"] }
orion-error = { version = "0.7", features = ["anyhow"] }
orion-error = { version = "0.7", features = ["toml"] }

Quick Start

use derive_more::From;
use orion_error::{
    prelude::*,
    reason::UvsReason,
    runtime::OperationContext,
};

#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum AppReason {
    #[orion_error(identity = "biz.invalid_request")]
    InvalidRequest,
    #[orion_error(transparent)]
    Uvs(UvsReason),
}

fn load_config(path: &str) -> Result<String, StructError<AppReason>> {
    let mut ctx = OperationContext::doing("load_config");
    ctx.record_field("path", path);

    std::fs::read_to_string(path)
        .into_as(AppReason::from(UvsReason::system_error()), "read config failed")
        .doing("read file")
        .with_context(&ctx)
}

What happens here:

  • AppReason is your domain reason enum
  • StructError<AppReason> is the runtime error carrier
  • into_as(...) converts a normal Rust error into the structured system
  • doing(...) and with_context(...) add operation context

For new code, treat doing(...) as the standard operation verb. want(...) only exists as a compatibility path for older code.

The 4 APIs To Learn First

  1. #[derive(OrionError)] Define stable business-facing reason enums.
  2. into_as(reason, detail) Use when a plain error enters the structured system for the first time.
  3. err_conv() Use when the upstream value is already StructError<R1> and you only remap reason type to StructError<R2>.
  4. wrap_as(reason, detail) Use when the upstream value is already StructError<_> and the upper layer wants a new semantic boundary.

Typical Flow

std::io::Error
  -> into_as(...)
StructError<RepoReason>
  -> err_conv() or wrap_as(...)
StructError<ServiceReason>
  -> report() / snapshot() / http_response(...)

This is the important shift:

  • lower layers do not invent random output shapes
  • middle layers do not lose source and context
  • boundary layers do not re-interpret raw strings
  • the whole system shares one governance model

Service Boundary Helpers

When you reach HTTP/RPC/log/CLI boundaries, these are the main entry points:

  • report() for human-oriented diagnostics
  • snapshot().stable_export() for stable machine export
  • http_response(...)
  • rpc_response(...)
  • cli_response(...)
  • log_response(...)
  • exposure_snapshot(...)

Current protocol naming is Exposure*, not ErrorPolicy*.

That matters because large systems usually fail at the boundary:

  • one team exposes too much detail
  • another team hides everything
  • every protocol builds its own error schema

orion-error gives those boundaries one consistent projection model.

Standard Error Bridge

StructError<R> no longer directly implements std::error::Error.

Use the explicit bridge APIs when you need that ecosystem:

let borrowed_std = err.as_std();
let owned_std = err.clone().into_std();
let boxed_std = err.into_boxed_std();

Recommended Imports

For new code, start with:

use orion_error::prelude::*;

Then add only the layered imports you need, for example:

  • orion_error::reason::UvsReason
  • orion_error::runtime::OperationContext
  • orion_error::report::*
  • orion_error::snapshot::*

This keeps application code simple while still letting larger codebases keep clear module boundaries.

Try It

cargo test --all-features -- --test-threads=1
cargo run --example order_case
cargo run --example logging_example --features log

Learn More

Maintainers

If publishing this crate family:

  1. publish orion-error-derive
  2. wait for crates.io index propagation
  3. publish orion-error