apollo-errors 0.7.0

Structured error handling with automatic format conversion
Documentation
//! Tower layer for error response rendering with content negotiation.

use super::{NegotiationConfig, service::ErrorService};
use tower::Layer;

/// Tower layer that wraps services to automatically render errors as HTTP responses.
///
/// This layer intercepts errors from your service and converts them into properly
/// formatted HTTP responses based on the client's `Accept` header. It supports
/// JSON, GraphQL, HTML, and plain text formats.
///
/// # Examples
///
/// ## Basic Usage
///
/// ```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 {
///         Ok::<_, Box<dyn std::error::Error>>(Response::new("OK"))
///     }));
/// ```
///
/// ## With Custom Configuration
///
/// ```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::Html);
///
/// 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"))
///     }));
/// ```
///
/// ## With Custom Error Types
///
/// ```rust
/// use apollo_errors::{Error, miette::Diagnostic, tower_http::ErrorLayer};
/// use tower::ServiceBuilder;
/// # use tower::service_fn;
/// # use http::{Request, Response};
///
/// #[derive(Debug, Error, Diagnostic)]
/// pub enum MyError {
///     #[error("Something went wrong")]
///     #[diagnostic(code(my::error))]
///     BadThing,
/// }
///
/// async fn handler(_req: Request<String>) -> Result<Response<String>, MyError> {
///     Err(MyError::BadThing)
/// }
///
/// # async fn example() {
/// let service = ServiceBuilder::new()
///     .layer(ErrorLayer::new())
///     .service_fn(handler);
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct ErrorLayer {
    config: NegotiationConfig,
}

impl ErrorLayer {
    /// Create a new error layer with default configuration.
    ///
    /// Default configuration:
    /// - Supports JSON, HTML, GraphQL, and plain text formats
    /// - Falls back to JSON when no Accept header matches
    /// - Respects quality values in Accept headers
    ///
    /// # Examples
    ///
    /// ```rust
    /// use apollo_errors::tower_http::ErrorLayer;
    ///
    /// let layer = ErrorLayer::new();
    /// ```
    pub fn new() -> Self {
        Self {
            config: NegotiationConfig::new(),
        }
    }

    /// Create a new error layer with custom configuration.
    ///
    /// Use this to customize content negotiation behavior, add custom media type
    /// mappings, or change the fallback renderer.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use apollo_errors::tower_http::{ErrorLayer, NegotiationConfig, Renderer};
    ///
    /// let config = NegotiationConfig::new()
    ///     .with_mapping("application/vnd.api+json", Renderer::Json)
    ///     .with_fallback(Renderer::Text);
    ///
    /// let layer = ErrorLayer::with_config(config);
    /// ```
    pub fn with_config(config: NegotiationConfig) -> Self {
        Self { config }
    }
}

impl Default for ErrorLayer {
    fn default() -> Self {
        Self::new()
    }
}

impl<S> Layer<S> for ErrorLayer {
    type Service = ErrorService<S>;

    fn layer(&self, inner: S) -> ErrorService<S> {
        ErrorService::new(inner, self.config.clone())
    }
}