cloudreve-api 0.8.4

A Rust library for interacting with Cloudreve API
Documentation
//! Error types for the Cloudreve API client

use crate::api::v4::models::AggregatedItemError;
use reqwest::Error as ReqwestError;
use std::collections::HashMap;
use std::io;
use thiserror::Error;

/// Main error type for the Cloudreve API client
#[derive(Error, Debug)]
pub enum Error {
    /// HTTP request error
    #[error("HTTP request error: {0}")]
    Http(#[from] ReqwestError),

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

    /// IO error
    #[error("IO error: {0}")]
    Io(#[from] io::Error),

    /// API error response
    #[error("API error: {message} (code: {code})")]
    Api { code: i32, message: String },

    /// API error whose payload the caller needs to act on
    ///
    /// Some business codes put actionable data in `data` — a lock conflict
    /// (40073) returns the unlock tokens there. [`Error::Api`] drops it, so
    /// endpoints whose callers must react to the payload report it here.
    #[error("API error: {message} (code: {code})")]
    ApiWithData {
        code: i32,
        message: String,
        data: serde_json::Value,
    },

    /// A batch operation completed only partially (code: 40081)
    ///
    /// `errors` is keyed by the URI that was sent; every URI absent from the
    /// map succeeded. Each entry carries its own business code, so a locked
    /// file surfaces as a 40073 sub-item with tokens in its `data`.
    #[error("Batch operation partially failed: {} of the submitted item(s) failed", errors.len())]
    Aggregate {
        code: i32,
        message: String,
        errors: HashMap<String, AggregatedItemError>,
    },

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

    /// Invalid response error
    #[error("Invalid response: {0}")]
    InvalidResponse(String),

    /// Invalid timestamp error
    #[error("Invalid timestamp: {0}")]
    InvalidTimestamp(String),

    /// Feature not supported in API version
    #[error("Feature '{0}' not supported in API {1}")]
    UnsupportedFeature(String, String),

    /// Two-factor authentication required (code: 203)
    /// Contains the session ID needed for 2FA completion
    #[error("Two-factor authentication required (session ID: {0})")]
    TwoFactorRequired(String),

    /// Unauthorized — application-level 401 returned in JSON body
    #[error("Unauthorized: {0}")]
    Unauthorized(String),

    /// CAPTCHA required for login
    #[error("CAPTCHA required for login")]
    CaptchaRequired,

    /// CAPTCHA validation failed
    #[error("CAPTCHA validation failed: {0}")]
    CaptchaInvalid(String),
}

impl Error {
    /// Server message of an API-level error, without the code decoration that
    /// [`std::fmt::Display`] adds.
    pub fn message(&self) -> Option<&str> {
        match self {
            Error::Api { message, .. }
            | Error::ApiWithData { message, .. }
            | Error::Aggregate { message, .. } => Some(message),
            _ => None,
        }
    }

    /// Business code carried by an API-level error, if any.
    pub fn code(&self) -> Option<i32> {
        match self {
            Error::Api { code, .. }
            | Error::ApiWithData { code, .. }
            | Error::Aggregate { code, .. } => Some(*code),
            _ => None,
        }
    }

    /// Response payload of an API-level error, if the variant kept one.
    pub fn data(&self) -> Option<&serde_json::Value> {
        match self {
            Error::ApiWithData { data, .. } => Some(data),
            _ => None,
        }
    }

    /// Per-item failures of a partially completed batch operation.
    pub fn aggregated_errors(&self) -> Option<&HashMap<String, AggregatedItemError>> {
        match self {
            Error::Aggregate { errors, .. } => Some(errors),
            _ => None,
        }
    }
}