//! Error types for the core SDK. use crate::api::{HumanVerification, ResponseCode}; pub type Result = std::result::Result; /// Top-level error for the core SDK. #[derive(Debug, thiserror::Error)] pub enum ProtonError { /// The API returned a non-success response envelope or HTTP status. #[error(transparent)] Api(#[from] ProtonApiError), /// Transport-level failure (DNS, TLS, timeout, connection reset). #[error("HTTP transport error: {0}")] Transport(#[from] reqwest::Error), /// Failed to (de)serialize a request or response body. #[error("serialization error: {0}")] Serialization(#[from] serde_json::Error), /// A cryptographic operation failed. #[error("cryptography error: {0}")] Crypto(#[from] crate::crypto::CryptoError), /// The SDK was used in a way that violates an invariant. #[error("invalid operation: {0}")] InvalidOperation(String), } impl ProtonError { pub fn invalid_operation(message: impl Into) -> Self { Self::InvalidOperation(message.into()) } } /// An error reported by the Proton API in its response envelope. #[derive(Debug, Clone, thiserror::Error)] #[error("proton api error {code:?} (http {http_status}): {message}")] pub struct ProtonApiError { /// Application-level response code from the `Code` field. pub code: ResponseCode, /// HTTP status code of the response. pub http_status: u16, /// Human-readable message from the `Error` field, if present. pub message: String, /// Raw `Details` object from the error envelope, when present. Endpoint /// specific — e.g. a revision-creation conflict names the existing draft. pub details: Option, } impl ProtonApiError { pub fn is_unauthorized(&self) -> bool { self.http_status == 401 } pub fn is_invalid_refresh_token(&self) -> bool { matches!(self.code, ResponseCode::InvalidRefreshToken) } /// The token lacks a scope the endpoint requires — no amount of refreshing /// fixes it; the user must authenticate with their password again. pub fn is_insufficient_scope(&self) -> bool { matches!(self.code, ResponseCode::InsufficientScope) } /// The request was gated behind human verification. pub fn is_human_verification_required(&self) -> bool { matches!(self.code, ResponseCode::HumanVerificationRequired) } /// The human-verification challenge, when this error carries one. /// /// `None` if the code is something else, or if the server gated the request /// without describing how to satisfy it — which is not recoverable in-app /// and should surface as a plain failure rather than an empty webview. pub fn human_verification(&self) -> Option { if !self.is_human_verification_required() { return None; } serde_json::from_value(self.details.clone()?).ok() } }