openmodex 0.1.1

Official Rust SDK for the OpenModex API
Documentation
use std::fmt;

/// The primary error type for the OpenModex SDK.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// An error returned by the OpenModex API.
    #[error("{0}")]
    Api(#[from] ApiError),

    /// An HTTP transport error.
    #[error("openmodex: request failed: {0}")]
    Http(#[from] reqwest::Error),

    /// JSON serialization or deserialization error.
    #[error("openmodex: json error: {0}")]
    Json(#[from] serde_json::Error),

    /// The API key was not provided.
    #[error("openmodex: API key is required (pass to OpenModex::new or set OPENMODEX_API_KEY)")]
    MissingApiKey,

    /// All fallback models failed.
    #[error("openmodex: all fallback models failed")]
    AllFallbacksFailed,

    /// An error that occurred during SSE stream parsing.
    #[error("openmodex: stream error: {0}")]
    Stream(String),

    /// Request timed out.
    #[error("openmodex: request timed out")]
    Timeout,
}

/// An error returned by the OpenModex API with status code and structured detail.
#[derive(Debug, Clone)]
pub struct ApiError {
    /// HTTP status code.
    pub status_code: u16,
    /// Machine-readable error code from the API.
    pub code: Option<String>,
    /// Error type classification from the API.
    pub error_type: Option<String>,
    /// Human-readable error message.
    pub message: String,
}

impl fmt::Display for ApiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.code {
            Some(code) => write!(f, "openmodex: {} {}: {}", self.status_code, code, self.message),
            None => write!(f, "openmodex: {}: {}", self.status_code, self.message),
        }
    }
}

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

impl ApiError {
    /// Returns `true` if this is a rate limit (429) error.
    pub fn is_rate_limited(&self) -> bool {
        self.status_code == 429
    }

    /// Returns `true` if this is an authentication (401) error.
    pub fn is_auth_error(&self) -> bool {
        self.status_code == 401
    }

    /// Returns `true` if this is a not-found (404) error.
    pub fn is_not_found(&self) -> bool {
        self.status_code == 404
    }

    /// Returns `true` if this is a server (5xx) error.
    pub fn is_server_error(&self) -> bool {
        self.status_code >= 500
    }
}

impl Error {
    /// Returns the inner [`ApiError`] if this is an API error variant.
    pub fn as_api_error(&self) -> Option<&ApiError> {
        match self {
            Error::Api(e) => Some(e),
            _ => None,
        }
    }

    /// Returns `true` if this is a rate limit (429) error.
    pub fn is_rate_limited(&self) -> bool {
        self.as_api_error().map_or(false, |e| e.is_rate_limited())
    }

    /// Returns `true` if this is an authentication (401) error.
    pub fn is_auth_error(&self) -> bool {
        self.as_api_error().map_or(false, |e| e.is_auth_error())
    }

    /// Returns `true` if this is a not-found (404) error.
    pub fn is_not_found(&self) -> bool {
        self.as_api_error().map_or(false, |e| e.is_not_found())
    }

    /// Returns `true` if this is a server error (5xx).
    pub fn is_server_error(&self) -> bool {
        self.as_api_error().map_or(false, |e| e.is_server_error())
    }

    /// Returns `true` if the error is retryable (429, 5xx, or network error).
    pub fn should_retry(&self) -> bool {
        match self {
            Error::Api(e) => {
                e.status_code == 429
                    || e.status_code == 500
                    || e.status_code == 502
                    || e.status_code == 503
                    || e.status_code == 504
            }
            Error::Http(_) | Error::Timeout => true,
            _ => false,
        }
    }

    /// Returns `true` if the error should trigger a fallback to the next model.
    pub(crate) fn should_fallback(&self) -> bool {
        match self {
            Error::Api(e) => e.status_code >= 500 || e.status_code == 429,
            Error::Http(_) | Error::Timeout => true,
            _ => false,
        }
    }
}