apollo-errors 0.7.0

Structured error handling with automatic format conversion
Documentation
//! Tower HTTP middleware for automatic error response rendering with content negotiation.
//!
//! This module provides a [Tower](https://github.com/tower-rs/tower) middleware layer that
//! automatically catches errors from your HTTP service and renders them in the format requested
//! by the client's `Accept` header.
//!
//! # Features
//!
//! - **Content Negotiation**: Automatically selects the best error format based on the `Accept` header
//! - **Multiple Formats**: Supports JSON, GraphQL, JSON-RPC, HTML, and plain text error responses
//! - **Quality Values**: Respects `q` values in Accept headers for format prioritization
//! - **Wildcard Matching**: Handles `*/*`, `text/*`, and `application/*` Accept patterns
//! - **Customizable**: Configure custom media type mappings and fallback renderers
//! - **Type-Safe**: Leverages the error registry for type-safe error rendering
//!
//! # Quick Start
//!
//! ```rust
//! use apollo_errors::tower_http::ErrorLayer;
//! use tower::ServiceBuilder;
//! # use tower::service_fn;
//! # use http::{Request, Response};
//!
//! let service = ServiceBuilder::new()
//!     .layer(ErrorLayer::new())
//!     .service(service_fn(|_req: Request<String>| async {
//!         // Your handler that might return errors
//!         Ok::<_, Box<dyn std::error::Error>>(Response::new("OK"))
//!     }));
//! ```
//!
//! # Content Negotiation
//!
//! The middleware examines the `Accept` header to determine the best format for error responses:
//!
//! | Accept Header | Format | Content-Type |
//! |--------------|--------|--------------|
//! | `application/json` | JSON | `application/json` |
//! | `text/html` | HTML | `text/html` |
//! | `application/graphql-response+json` | GraphQL | `application/graphql-response+json` |
//! | `application/json-rpc` | JSON-RPC | `application/json-rpc` |
//! | `text/plain` | Plain text | `text/plain` |
//! | `*/*` or missing | JSON (default) | `application/json` |
//!
//! ## Quality Values
//!
//! The middleware respects quality values (`q` parameters) in Accept headers:
//!
//! ```text
//! Accept: text/html;q=0.9, application/json;q=0.8
//! → Returns HTML (higher quality)
//!
//! Accept: application/json, text/html;q=0.5
//! → Returns JSON (equal quality, JSON is more specific)
//! ```
//!
//! ## Wildcards
//!
//! Wildcard patterns are supported and follow HTTP content negotiation rules:
//!
//! ```text
//! Accept: text/*
//! → Matches text/html or text/plain
//!
//! Accept: application/*
//! → Matches application/json or application/graphql-response+json
//!
//! Accept: */*
//! → Matches any format (falls back to default)
//! ```
//!
//! # Custom Configuration
//!
//! You can customize the content negotiation behavior:
//!
//! ```rust
//! use apollo_errors::tower_http::{ErrorLayer, NegotiationConfig, Renderer};
//! use tower::ServiceBuilder;
//! # use tower::service_fn;
//! # use http::{Request, Response};
//!
//! let config = NegotiationConfig::new()
//!     .with_mapping("application/vnd.api+json", Renderer::Json)
//!     .with_fallback(Renderer::Text);
//!
//! let service = ServiceBuilder::new()
//!     .layer(ErrorLayer::with_config(config))
//!     .service(service_fn(|_req: Request<String>| async {
//!         Ok::<_, Box<dyn std::error::Error>>(Response::new("OK"))
//!     }));
//! ```
//!
//! # Integration Example
//!
//! Complete example showing error handling with custom error types:
//!
//! ```rust
//! use apollo_errors::{Error, miette::Diagnostic, tower_http::ErrorLayer};
//! use tower::ServiceBuilder;
//! use http::{Request, Response, StatusCode};
//! # use tower::service_fn;
//!
//! #[derive(Debug, Error, Diagnostic)]
//! pub enum ApiError {
//!     #[error("Resource not found: {resource}")]
//!     #[diagnostic(code(api::not_found))]
//!     #[http_status(404)]
//!     NotFound {
//!         #[extension]
//!         resource: String,
//!     },
//!
//!     #[error("Invalid request: {reason}")]
//!     #[diagnostic(code(api::bad_request))]
//!     #[http_status(400)]
//!     BadRequest {
//!         #[extension]
//!         reason: String,
//!     },
//! }
//!
//! async fn handler(_req: Request<String>) -> Result<Response<String>, ApiError> {
//!     Err(ApiError::NotFound {
//!         resource: "user/123".to_string(),
//!     })
//! }
//!
//! # async fn example() {
//! let service = ServiceBuilder::new()
//!     .layer(ErrorLayer::new())
//!     .service_fn(handler);
//! # }
//! ```
//!
//! The middleware will:
//! 1. Catch the `ApiError::NotFound` error
//! 2. Check the `Accept` header from the request
//! 3. Render the error in the requested format (JSON/GraphQL/HTML/Text)
//! 4. Set the appropriate `Content-Type` header
//! 5. Set the HTTP status code from the error (404 in this case)
//!
//! # Error Response Format
//!
//! ## JSON Format
//!
//! ```json
//! {
//!   "errors": [{
//!     "message": "Resource not found: user/123",
//!     "extensions": {
//!       "code": "api::not_found",
//!       "resource": "user/123"
//!     }
//!   }]
//! }
//! ```
//!
//! ## GraphQL Format
//!
//! ```json
//! {
//!   "errors": [{
//!     "message": "Resource not found: user/123",
//!     "extensions": {
//!       "code": "api::not_found",
//!       "resource": "user/123"
//!     }
//!   }]
//! }
//! ```
//!
//! ## HTML Format
//!
//! ```html
//! <!DOCTYPE html>
//! <html>
//! <head><title>Error: api::not_found</title></head>
//! <body>
//!   <h1>api::not_found</h1>
//!   <p>Resource not found: user/123</p>
//!   <h2>Details</h2>
//!   <dl>
//!     <dt>resource</dt>
//!     <dd>user/123</dd>
//!   </dl>
//! </body>
//! </html>
//! ```
//!
//! ## Text Format
//!
//! ```text
//! Error: api::not_found
//! Resource not found: user/123
//!
//! Details:
//!   resource: user/123
//! ```

mod body;
mod future;
mod layer;
mod negotiate;
mod service;

pub use body::ErrorBody;
pub use layer::ErrorLayer;
pub use negotiate::{NegotiationConfig, Renderer};
pub use service::ErrorService;