payrust 0.1.0

PayPal REST API client for Rust
Documentation
use std::fmt;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("HTTP error: {0}")]
    Http(#[from] reqwest::Error),

    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("PayPal API error: {message}")]
    Api {
        message: String,
        debug_id: Option<String>,
        details: Vec<ErrorDetail>,
    },

    #[error("Authentication failed: {0}")]
    Auth(String),

    #[error("Token expired")]
    TokenExpired,

    #[error("Invalid configuration: {0}")]
    Config(String),

    #[error("Webhook verification failed: {0}")]
    WebhookVerification(String),
}

#[derive(Debug, Clone, serde::Deserialize)]
pub struct ErrorDetail {
    pub field: Option<String>,
    pub value: Option<String>,
    pub issue: String,
    pub description: Option<String>,
}

impl fmt::Display for ErrorDetail {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(field) = &self.field {
            write!(f, "[{}] ", field)?;
        }
        write!(f, "{}", self.issue)?;
        if let Some(desc) = &self.description {
            write!(f, " - {}", desc)?;
        }
        Ok(())
    }
}

#[derive(Debug, serde::Deserialize)]
pub(crate) struct ApiErrorResponse {
    #[allow(dead_code)]
    pub name: Option<String>,
    pub message: Option<String>,
    pub debug_id: Option<String>,
    #[serde(default)]
    pub details: Vec<ErrorDetail>,
    pub error: Option<String>,
    pub error_description: Option<String>,
}

impl From<ApiErrorResponse> for Error {
    fn from(resp: ApiErrorResponse) -> Self {
        if let Some(error) = resp.error {
            return Error::Auth(resp.error_description.unwrap_or(error));
        }

        Error::Api {
            message: resp.message.unwrap_or_else(|| "Unknown error".to_string()),
            debug_id: resp.debug_id,
            details: resp.details,
        }
    }
}