apollo-errors 0.7.0

Structured error handling with automatic format conversion
Documentation
//! Common test error types shared across test files

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

/// Simple error with just a message
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum SimpleError {
    #[error("Something went wrong")]
    #[diagnostic(code(errors::simple))]
    Simple,

    #[error("Another error occurred")]
    #[diagnostic(code(errors::another))]
    Another,
}

/// Error with extension fields
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum ErrorWithFields {
    #[error("Invalid port")]
    #[diagnostic(code(config::invalid_port))]
    InvalidPort {
        #[extension]
        port: u16,

        #[extension]
        config_file: String,
    },

    #[error("Missing configuration")]
    #[diagnostic(code(config::missing))]
    MissingConfig {
        #[extension]
        expected_path: String,
    },
}

/// Error with custom HTTP status codes
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum ErrorWithStatus {
    #[error("Resource not found")]
    #[diagnostic(code(resource::not_found))]
    #[http_status(404)]
    NotFound,

    #[error("Bad request")]
    #[diagnostic(code(request::bad))]
    #[http_status(400)]
    BadRequest,

    #[error("Service unavailable")]
    #[diagnostic(code(service::unavailable))]
    #[http_status(503)]
    ServiceUnavailable,

    #[error("Internal error")]
    #[diagnostic(code(internal::error))]
    InternalError, // Default 500
}

/// Error with help text, url, and severity
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum ErrorWithHelp {
    #[error("Configuration error")]
    #[diagnostic(
        code(config::error),
        help("Check your configuration file"),
        url("https://docs.example.com/errors/config"),
        severity(warning)
    )]
    ConfigError,
}

/// Inner error type for transparent variant testing
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum InnerError {
    #[error("Database connection failed")]
    #[diagnostic(code(db::connection_failed), help("Check your database credentials"))]
    #[http_status(503)]
    DatabaseError,

    #[error("Network timeout after {timeout_ms}ms")]
    #[diagnostic(code(network::timeout))]
    #[http_status(504)]
    NetworkTimeout {
        #[extension]
        timeout_ms: u64,
    },
}

/// Error with transparent variant that wraps InnerError
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum TransparentWrapperError {
    #[error("Application error")]
    #[diagnostic(code(app::error))]
    ApplicationError,

    #[diagnostic(transparent)]
    Inner(InnerError),
}

/// Simple struct error for testing
#[derive(Debug, Error, Diagnostic, Clone)]
#[error("Simple struct error occurred")]
#[diagnostic(code(structs::simple))]
pub struct SimpleStructError;

/// Struct error with fields
#[derive(Debug, Error, Diagnostic, Clone)]
#[error("Configuration error: invalid port {port}")]
#[diagnostic(code(structs::config_error), help("Check your configuration file"))]
#[http_status(400)]
pub struct ConfigStructError {
    #[extension]
    pub port: u16,

    #[extension]
    pub config_path: String,
}

/// Enum with transparent variant wrapping a struct error
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum TransparentToStructError {
    #[error("Direct enum error")]
    #[diagnostic(code(wrapper::direct))]
    Direct,

    #[diagnostic(transparent)]
    Config(ConfigStructError),

    #[diagnostic(transparent)]
    Simple(SimpleStructError),
}

/// Error with custom JSON-RPC codes
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum JsonRpcError {
    #[error("Method not found: {method}")]
    #[diagnostic(code(jsonrpc::method_not_found))]
    #[jsonrpc_code(-32601)]
    MethodNotFound {
        #[extension]
        method: String,
    },

    #[error("No code specified, uses default")]
    #[diagnostic(code(jsonrpc::default_code))]
    DefaultCode,
}

/// Struct error with JSON-RPC code
#[derive(Debug, Error, Diagnostic, Clone)]
#[error("RPC validation failed for field {field}")]
#[diagnostic(code(rpc::validation_error))]
#[jsonrpc_code(-32602)]
pub struct RpcValidationError {
    #[extension]
    pub field: String,

    #[extension]
    pub reason: String,
}

/// Enum error with optional extension fields
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum ErrorWithOptionalFields {
    #[error("Request failed")]
    #[diagnostic(code(request::failed))]
    RequestFailed {
        #[extension]
        request_id: Option<String>,

        #[extension]
        retry_after: Option<u64>,
    },

    #[error("Partial failure")]
    #[diagnostic(code(request::partial))]
    PartialFailure {
        #[extension]
        succeeded: u32,

        #[extension]
        failed: Option<u32>,
    },
}

/// Struct error with optional fields
#[derive(Debug, Error, Diagnostic, Clone)]
#[error("Validation failed for {field}")]
#[diagnostic(code(validation::failed))]
pub struct ValidationError {
    #[extension]
    pub field: String,

    #[extension]
    pub suggestion: Option<String>,
}

/// Error with HTTP headers
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum ErrorWithHeaders {
    #[error("Rate limit exceeded")]
    #[diagnostic(code(rate_limit::exceeded))]
    #[http_status(429)]
    RateLimitExceeded {
        #[http_header("Retry-After")]
        retry_after: u64,

        #[http_header("X-RateLimit-Remaining")]
        remaining: u32,
    },

    #[error("Quota warning")]
    #[diagnostic(code(rate_limit::warning))]
    QuotaWarning {
        #[http_header("X-RateLimit-Remaining")]
        remaining: Option<u32>,
    },

    #[error("No headers")]
    #[diagnostic(code(simple::error))]
    NoHeaders,
}

/// Struct error with HTTP headers
#[derive(Debug, Error, Diagnostic, Clone)]
#[error("Rate limit error")]
#[diagnostic(code(rate_limit::struct_error))]
#[http_status(429)]
pub struct RateLimitStructError {
    #[http_header("Retry-After")]
    pub retry_after: u64,

    #[extension]
    pub limit: u32,
}

/// Inner error with headers for transparent testing
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum InnerErrorWithHeaders {
    #[error("Inner rate limit")]
    #[diagnostic(code(inner::rate_limit))]
    #[http_status(429)]
    RateLimit {
        #[http_header("Retry-After")]
        retry_after: u64,
    },
}

/// Wrapper with transparent variant for header propagation testing
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum TransparentWithHeaders {
    #[error("Direct error")]
    #[diagnostic(code(wrapper::direct))]
    Direct,

    #[diagnostic(transparent)]
    Inner(InnerErrorWithHeaders),
}

/// Error with a boolean http_header field
#[derive(Debug, Error, Diagnostic, Clone)]
#[error("Feature flagged")]
#[diagnostic(code(feature::flagged))]
pub struct BoolHeaderError {
    #[http_header("X-Feature-Enabled")]
    pub enabled: bool,
}

/// Error with an explicitly renamed extension field
#[derive(Debug, Error, Diagnostic, Clone)]
#[allow(dead_code)]
pub enum ErrorWithRenamedField {
    #[error("Field was renamed")]
    #[diagnostic(code(test::renamed_field))]
    WithRename {
        #[extension(rename = "myRenamedField")]
        my_field: String,
    },
}