lmrc-cloudflare 0.3.16

Cloudflare API client library for the LMRC Stack - comprehensive DNS, zones, and cache management with automatic retry logic
Documentation
//! Error types for the Cloudflare client.

use std::fmt;

/// Result type alias for Cloudflare operations.
pub type Result<T> = std::result::Result<T, Error>;

/// The main error type for Cloudflare client operations.
#[derive(Debug)]
pub enum Error {
    /// HTTP request failed
    Http(reqwest::Error),

    /// Cloudflare API returned an error response
    Api(ApiError),

    /// Failed to serialize/deserialize JSON
    Json(serde_json::Error),

    /// Resource not found (zone, record, etc.)
    NotFound(String),

    /// Invalid input or configuration
    InvalidInput(String),

    /// Authentication failed
    Unauthorized(String),

    /// Rate limited by Cloudflare API
    RateLimited { retry_after: Option<u64> },
}

/// Detailed API error from Cloudflare
#[derive(Debug, Clone)]
pub struct ApiError {
    /// HTTP status code
    pub status: u16,

    /// Error code from Cloudflare
    pub code: Option<i32>,

    /// Error message
    pub message: String,

    /// Full response body for debugging
    pub body: String,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Http(e) => write!(f, "HTTP request failed: {}", e),
            Error::Api(e) => write!(f, "Cloudflare API error: {}", e.message),
            Error::Json(e) => write!(f, "JSON serialization error: {}", e),
            Error::NotFound(msg) => write!(f, "Resource not found: {}", msg),
            Error::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
            Error::Unauthorized(msg) => write!(f, "Authentication failed: {}", msg),
            Error::RateLimited { retry_after } => {
                if let Some(seconds) = retry_after {
                    write!(f, "Rate limited, retry after {} seconds", seconds)
                } else {
                    write!(f, "Rate limited by Cloudflare API")
                }
            }
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::Http(e) => Some(e),
            Error::Json(e) => Some(e),
            _ => None,
        }
    }
}

impl From<reqwest::Error> for Error {
    fn from(err: reqwest::Error) -> Self {
        Error::Http(err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Json(err)
    }
}

impl ApiError {
    /// Create a new API error
    pub fn new(status: u16, message: String, body: String) -> Self {
        Self {
            status,
            code: None,
            message,
            body,
        }
    }

    /// Create from Cloudflare API response
    pub(crate) fn from_response(status: u16, body: &str) -> Self {
        // Try to parse error details from response
        if let Ok(json) = serde_json::from_str::<serde_json::Value>(body)
            && let Some(errors) = json["errors"].as_array()
            && let Some(first_error) = errors.first()
        {
            return Self {
                status,
                code: first_error["code"].as_i64().map(|c| c as i32),
                message: first_error["message"]
                    .as_str()
                    .unwrap_or("Unknown error")
                    .to_string(),
                body: body.to_string(),
            };
        }

        Self {
            status,
            code: None,
            message: format!("HTTP {}", status),
            body: body.to_string(),
        }
    }
}