Skip to main content

proton_sdk/
api.rs

1//! API response envelope and response codes shared across all Proton endpoints.
2
3use serde::{Deserialize, Deserializer};
4
5/// The common envelope every Proton API JSON response embeds.
6///
7/// Successful responses carry `Code == 1000` plus their endpoint-specific
8/// fields; failures carry a non-success code and an `Error` message.
9#[derive(Debug, Clone, Deserialize)]
10pub struct ApiResponse {
11    #[serde(rename = "Code")]
12    pub code: ResponseCode,
13
14    #[serde(rename = "Error", default)]
15    pub error_message: Option<String>,
16}
17
18impl ApiResponse {
19    pub fn is_success(&self) -> bool {
20        matches!(self.code, ResponseCode::Success)
21    }
22}
23
24/// Application-level response codes returned in the `Code` field.
25///
26/// Mirrors `Proton.Sdk.Api.ResponseCode`. Unknown / future codes deserialize to
27/// [`ResponseCode::Unknown`] via [`ResponseCode::from_raw`] rather than failing.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[repr(i64)]
30pub enum ResponseCode {
31    Unknown = 0,
32
33    Unauthorized = 401,
34    Forbidden = 403,
35    RequestTimeout = 408,
36    TooManyRequests = 429,
37    ServiceUnavailable = 503,
38
39    Success = 1000,
40    MultipleResponses = 1001,
41    InvalidRequirements = 2000,
42    InvalidValue = 2001,
43    NotEnoughPermissions = 2011,
44    NotEnoughPermissionsToGrantPermissions = 2026,
45    InvalidEncryptedIdFormat = 2061,
46    AlreadyExists = 2500,
47    DoesNotExist = 2501,
48    Timeout = 2503,
49    IncompatibleState = 2511,
50    InvalidApp = 5002,
51    OutdatedApp = 5003,
52    Offline = 7001,
53    IncorrectLoginCredentials = 8002,
54    /// The access token lacks a scope the endpoint requires. Notably
55    /// `core/v4/keys/salts` requires `locked`, which only a
56    /// password-authenticated token carries — not one from `auth/v4/refresh`.
57    InsufficientScope = 9101,
58    AccountDeleted = 10_002,
59    AccountDisabled = 10_003,
60    InvalidRefreshToken = 10_013,
61    NoActiveSubscription = 22_110,
62    AddressMissing = 33_102,
63    DomainExternal = 33_103,
64    ProtonDriveUnknown = 200_000,
65    InsufficientQuota = 200_001,
66    InsufficientSpace = 200_002,
67    InsufficientVolumeQuota = 200_100,
68    TooManyChildren = 200_300,
69    NestingTooDeep = 200_301,
70}
71
72impl ResponseCode {
73    /// Map a raw integer code to a known variant, falling back to [`Self::Unknown`].
74    pub fn from_raw(raw: i64) -> Self {
75        match raw {
76            401 => Self::Unauthorized,
77            403 => Self::Forbidden,
78            408 => Self::RequestTimeout,
79            429 => Self::TooManyRequests,
80            503 => Self::ServiceUnavailable,
81            1000 => Self::Success,
82            1001 => Self::MultipleResponses,
83            2000 => Self::InvalidRequirements,
84            2001 => Self::InvalidValue,
85            2011 => Self::NotEnoughPermissions,
86            2026 => Self::NotEnoughPermissionsToGrantPermissions,
87            2061 => Self::InvalidEncryptedIdFormat,
88            2500 => Self::AlreadyExists,
89            2501 => Self::DoesNotExist,
90            2503 => Self::Timeout,
91            2511 => Self::IncompatibleState,
92            5002 => Self::InvalidApp,
93            5003 => Self::OutdatedApp,
94            7001 => Self::Offline,
95            8002 => Self::IncorrectLoginCredentials,
96            9101 => Self::InsufficientScope,
97            10_002 => Self::AccountDeleted,
98            10_003 => Self::AccountDisabled,
99            10_013 => Self::InvalidRefreshToken,
100            22_110 => Self::NoActiveSubscription,
101            33_102 => Self::AddressMissing,
102            33_103 => Self::DomainExternal,
103            200_000 => Self::ProtonDriveUnknown,
104            200_001 => Self::InsufficientQuota,
105            200_002 => Self::InsufficientSpace,
106            200_100 => Self::InsufficientVolumeQuota,
107            200_300 => Self::TooManyChildren,
108            200_301 => Self::NestingTooDeep,
109            _ => Self::Unknown,
110        }
111    }
112}
113
114impl<'de> Deserialize<'de> for ResponseCode {
115    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
116    where
117        D: Deserializer<'de>,
118    {
119        let raw = i64::deserialize(deserializer)?;
120        Ok(ResponseCode::from_raw(raw))
121    }
122}