emailit 2.0.3

The official Rust SDK for the Emailit Email API
Documentation
//! Error types for the Emailit SDK.

use std::fmt;

/// Details of an API error response.
///
/// Every non-connection [`Error`] variant carries an `ApiError` with the HTTP
/// status code, a human-readable message, and the raw response body.
#[derive(Debug)]
pub struct ApiError {
    /// HTTP status code returned by the API (e.g. 401, 422, 429).
    pub status: u16,
    /// Human-readable error message extracted from the response.
    pub message: String,
    /// Raw response body, useful for debugging.
    pub body: String,
}

/// Typed error returned by every SDK method.
///
/// Each variant maps to a category of failure so you can match on
/// the kind of error without inspecting status codes manually.
///
/// # Example
///
/// ```no_run
/// # async fn example() {
/// # let emailit = emailit::Emailit::new("key");
/// match emailit.emails.get("id").await {
///     Ok(email) => println!("got email: {:?}", email),
///     Err(e) if e.is_authentication() => eprintln!("bad API key"),
///     Err(e) if e.is_rate_limit() => eprintln!("slow down!"),
///     Err(e) => eprintln!("error: {e}"),
/// }
/// # }
/// ```
#[derive(Debug)]
pub enum Error {
    /// HTTP 401 -- invalid or missing API key.
    Authentication(ApiError),
    /// HTTP 400 or 404 -- bad request or resource not found.
    InvalidRequest(ApiError),
    /// HTTP 422 -- validation error.
    UnprocessableEntity(ApiError),
    /// HTTP 429 -- rate limit exceeded.
    RateLimit(ApiError),
    /// Any other non-2xx status code.
    Api(ApiError),
    /// Network / connection failure (no HTTP response received).
    Connection(String),
}

impl Error {
    /// Returns `true` if this is an authentication error (HTTP 401).
    pub fn is_authentication(&self) -> bool {
        matches!(self, Error::Authentication(_))
    }

    /// Returns `true` if this is an invalid request error (HTTP 400/404).
    pub fn is_invalid_request(&self) -> bool {
        matches!(self, Error::InvalidRequest(_))
    }

    /// Returns `true` if this is a validation error (HTTP 422).
    pub fn is_unprocessable_entity(&self) -> bool {
        matches!(self, Error::UnprocessableEntity(_))
    }

    /// Returns `true` if this is a rate-limit error (HTTP 429).
    pub fn is_rate_limit(&self) -> bool {
        matches!(self, Error::RateLimit(_))
    }

    /// Returns `true` if this is a connection/network error.
    pub fn is_connection(&self) -> bool {
        matches!(self, Error::Connection(_))
    }

    /// Returns the underlying [`ApiError`] if this error originated from an
    /// HTTP response, or `None` for connection errors.
    pub fn api_error(&self) -> Option<&ApiError> {
        match self {
            Error::Authentication(e)
            | Error::InvalidRequest(e)
            | Error::UnprocessableEntity(e)
            | Error::RateLimit(e)
            | Error::Api(e) => Some(e),
            Error::Connection(_) => None,
        }
    }

    /// Returns the HTTP status code if available, or `None` for connection errors.
    pub fn status(&self) -> Option<u16> {
        self.api_error().map(|e| e.status)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Authentication(e) => write!(f, "{}", e.message),
            Error::InvalidRequest(e) => write!(f, "{}", e.message),
            Error::UnprocessableEntity(e) => write!(f, "{}", e.message),
            Error::RateLimit(e) => write!(f, "{}", e.message),
            Error::Api(e) => write!(f, "{}", e.message),
            Error::Connection(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for Error {}

/// Creates a typed [`Error`] from an HTTP status code and response details.
///
/// The status code determines which variant is returned:
/// 401 -> [`Error::Authentication`], 400/404 -> [`Error::InvalidRequest`],
/// 422 -> [`Error::UnprocessableEntity`], 429 -> [`Error::RateLimit`],
/// anything else -> [`Error::Api`].
pub fn new_api_error(status: u16, message: String, body: String) -> Error {
    let api_error = ApiError {
        status,
        message,
        body,
    };

    match status {
        401 => Error::Authentication(api_error),
        400 | 404 => Error::InvalidRequest(api_error),
        422 => Error::UnprocessableEntity(api_error),
        429 => Error::RateLimit(api_error),
        _ => Error::Api(api_error),
    }
}

/// Creates an [`Error::Connection`] with a descriptive message.
pub fn new_connection_error(message: impl Into<String>) -> Error {
    Error::Connection(format!(
        "Could not connect to the Emailit API: {}",
        message.into()
    ))
}