apollo-errors 0.6.0

Structured error handling with automatic format conversion
Documentation

Structured error handling with multi-format output.

Define errors once and render them to JSON, GraphQL, HTML, or plain text.

Quick Start

use apollo_errors::Error;
use miette::Diagnostic;

#[derive(Debug, Error, Diagnostic)]
pub enum AuthError {
    #[error("Invalid credentials for user {username}")]
    #[diagnostic(code(auth::invalid_credentials))]
    InvalidCredentials {
        #[extension]
        username: String,
    },
}

let error = AuthError::InvalidCredentials {
    username: "alice".to_string(),
};

// Render to different formats
let json = error.to_json().unwrap();
let graphql = error.to_graphql().unwrap();
let jsonrpc = error.to_jsonrpc().unwrap();
let html = error.to_html();
let text = error.to_text();

Defining Errors

Errors require three derives: Debug, Error, and Diagnostic.

#[derive(Debug, Error, Diagnostic)]
pub enum MyError {
    #[error("Something went wrong")]
    #[diagnostic(code(service::something_wrong))]
    SomethingWrong,
}

Attributes

Error Message

Use #[error("...")] to define the error message. Field interpolation is supported:

#[error("Failed to connect to {host}:{port}")]
ConnectionFailed { host: String, port: u16 },

Error Code

Use #[diagnostic(code(...))] to define a unique error code. Codes must have at least two :: separated segments, all lowercase:

#[diagnostic(code(db::connection_failed))]
#[diagnostic(code(auth::invalid_token))]

Extension Fields

Mark fields with #[extension] to include them in JSON and GraphQL output:

InvalidPort {
    #[extension]
    port: u16,
    #[extension]
    config_file: String,
},

HTTP Status

Specify an HTTP status code (defaults to 500):

#[http_status(404)]
NotFound,

HTTP Headers

Mark fields to be returned as HTTP response headers. Supported types are u16, u32, u64, i16, i32, i64, bool, and HeaderValue:

#[http_status(429)]
RateLimitExceeded {
    #[http_header("Retry-After")]
    retry_after: u64,
    #[http_header("X-RateLimit-Remaining")]
    remaining: u32,
},

Header names are validated at compile time against RFC 7230. Headers are automatically set when using [tower_http::ErrorLayer]. For Option<T> fields, the header is only included when the value is Some.

JSON-RPC Code

Specify a JSON-RPC 2.0 error code (defaults to -32000, "Server error"):

#[jsonrpc_code(-32602)]
InvalidParams { param: String },

Reserved JSON-RPC codes:

  • -32700: Parse error
  • -32600: Invalid Request
  • -32601: Method not found
  • -32602: Invalid params
  • -32603: Internal error
  • -32000 to -32099: Server error (available for application use)

Help Text and URLs

Provide additional context for users:

#[diagnostic(
    code(config::invalid),
    help("Check your configuration file"),
    url("https://docs.example.com/errors/config-invalid")
)]

Error Chaining

Use #[source] to chain errors, or #[from] to also generate a From impl:

#[error("Database operation failed")]
#[diagnostic(code(db::operation_failed))]
DatabaseError {
    #[from]
    source: std::io::Error,
},

Dynamic Dispatch

Format any std::error::Error using the extension traits:

use apollo_errors::{ErrorExt, HeapErrorExt};

fn handle_error(error: Box<dyn std::error::Error + Send + Sync>) {
    let json = error.to_json().unwrap();
    println!("{}", json);
}