Skip to main content

comdirect_rest_api/
error.rs

1use reqwest::StatusCode;
2use thiserror::Error;
3
4/// Result alias used by all public operations of this crate.
5pub type Result<T> = std::result::Result<T, ComdirectError>;
6
7/// Errors produced by the comdirect API client.
8#[derive(Debug, Error)]
9pub enum ComdirectError {
10    #[error("network error during {operation}: {source}")]
11    Network {
12        operation: &'static str,
13        #[source]
14        source: reqwest::Error,
15    },
16
17    #[error("api status {status} for {url} during {operation}")]
18    ApiStatus {
19        operation: &'static str,
20        url: String,
21        status: StatusCode,
22        body: String,
23    },
24
25    #[error("session is not authorized")]
26    NotAuthorized,
27
28    #[error("missing field '{field}' in {context}")]
29    MissingField {
30        field: &'static str,
31        context: &'static str,
32        body: String,
33    },
34
35    #[error("failed to parse JSON in {context}: {source}")]
36    JsonParse {
37        context: &'static str,
38        #[source]
39        source: serde_json::Error,
40        body: String,
41    },
42
43    #[error("invalid header '{name}': {message}")]
44    InvalidHeader { name: &'static str, message: String },
45
46    #[error("invalid input for '{field}': {message}")]
47    InvalidInput {
48        field: &'static str,
49        message: String,
50    },
51
52    #[error("login cancelled by callback")]
53    LoginCancelled,
54
55    #[error("auth flow error: {0}")]
56    AuthFlow(String),
57
58    #[error("background task '{task_name}' panicked: {message}")]
59    TaskPanicked {
60        task_name: &'static str,
61        message: String,
62    },
63}
64
65impl ComdirectError {
66    pub(crate) fn network(operation: &'static str, source: reqwest::Error) -> Self {
67        Self::Network { operation, source }
68    }
69
70    pub(crate) fn api_status(
71        operation: &'static str,
72        url: impl Into<String>,
73        status: StatusCode,
74        body: String,
75    ) -> Self {
76        Self::ApiStatus {
77            operation,
78            url: url.into(),
79            status,
80            body,
81        }
82    }
83
84    pub(crate) fn json_parse(
85        context: &'static str,
86        source: serde_json::Error,
87        body: String,
88    ) -> Self {
89        Self::JsonParse {
90            context,
91            source,
92            body,
93        }
94    }
95
96    pub(crate) fn is_transient(&self) -> bool {
97        match self {
98            Self::Network { source, .. } => {
99                source.is_timeout() || source.is_connect() || source.is_request()
100            }
101            Self::ApiStatus { status, .. } => {
102                *status == StatusCode::REQUEST_TIMEOUT
103                    || *status == StatusCode::TOO_MANY_REQUESTS
104                    || status.is_server_error()
105            }
106            _ => false,
107        }
108    }
109}