1use thiserror::Error;
4
5use crate::rest;
6
7#[allow(missing_docs)]
9#[derive(Debug, Error)]
10pub enum RestClientError {
11 #[error("invalid request: {message}")]
12 InvalidRequest { message: String },
13
14 #[error("reqwest timeout: {source}")]
15 ReqwestTimeout {
16 #[source]
17 source: reqwest::Error,
18 },
19
20 #[error("reqwest connect error: {source}")]
21 ReqwestConnect {
22 #[source]
23 source: reqwest::Error,
24 },
25
26 #[error("reqwest request construction error: {source}")]
27 ReqwestRequest {
28 #[source]
29 source: reqwest::Error,
30 },
31
32 #[error("reqwest redirect error: {source}")]
33 ReqwestRedirect {
34 #[source]
35 source: reqwest::Error,
36 },
37
38 #[error("reqwest HTTP status error: {source}")]
39 ReqwestStatus {
40 #[source]
41 source: reqwest::Error,
42 },
43
44 #[error("reqwest response body error: {source}")]
45 ReqwestBody {
46 #[source]
47 source: reqwest::Error,
48 },
49
50 #[error("reqwest decode error: {source}")]
51 ReqwestDecode {
52 #[source]
53 source: reqwest::Error,
54 },
55
56 #[error("reqwest error: {source}")]
57 ReqwestOther {
58 #[source]
59 source: reqwest::Error,
60 },
61
62 #[error("documented API error response: HTTP {status} {error_code}: {error_message}")]
63 ApiErrorResponse {
64 status: reqwest::StatusCode,
65 error_code: String,
66 error_message: String,
67 },
68
69 #[error("undocumented error response: HTTP {status}")]
70 UndocumentedErrorResponse { status: reqwest::StatusCode },
71
72 #[error("invalid response payload: {source}")]
73 InvalidResponsePayload {
74 #[source]
75 source: serde_json::Error,
76 },
77
78 #[error("unexpected response: HTTP {status}")]
79 UnexpectedResponse { status: reqwest::StatusCode },
80
81 #[error("{message}")]
82 Custom { message: String },
83}
84
85fn reqwest_error(source: reqwest::Error) -> RestClientError {
86 if source.is_timeout() {
87 RestClientError::ReqwestTimeout { source }
88 } else if source.is_connect() {
89 RestClientError::ReqwestConnect { source }
90 } else if source.is_request() {
91 RestClientError::ReqwestRequest { source }
92 } else if source.is_redirect() {
93 RestClientError::ReqwestRedirect { source }
94 } else if source.is_status() {
95 RestClientError::ReqwestStatus { source }
96 } else if source.is_body() {
97 RestClientError::ReqwestBody { source }
98 } else if source.is_decode() {
99 RestClientError::ReqwestDecode { source }
100 } else {
101 RestClientError::ReqwestOther { source }
102 }
103}
104
105fn map_rest_error<T>(
106 source: rest::Error<T>,
107 to_api_fields: impl FnOnce(T) -> Option<(String, String)>,
108) -> RestClientError {
109 match source {
110 rest::Error::InvalidRequest(message) => RestClientError::InvalidRequest { message },
111 rest::Error::CommunicationError(source)
112 | rest::Error::InvalidUpgrade(source)
113 | rest::Error::ResponseBodyError(source) => reqwest_error(source),
114 rest::Error::ErrorResponse(response) => {
115 let status = response.status();
116 let response_body = response.into_inner();
117 if let Some((error_code, error_message)) = to_api_fields(response_body) {
118 RestClientError::ApiErrorResponse {
119 status,
120 error_code,
121 error_message,
122 }
123 } else {
124 RestClientError::UndocumentedErrorResponse { status }
125 }
126 }
127 rest::Error::InvalidResponsePayload(_, source) => {
128 RestClientError::InvalidResponsePayload { source }
129 }
130 rest::Error::UnexpectedResponse(response) => RestClientError::UnexpectedResponse {
131 status: response.status(),
132 },
133 rest::Error::Custom(message) => RestClientError::Custom { message },
134 }
135}
136
137impl From<rest::Error<()>> for RestClientError {
138 fn from(source: rest::Error<()>) -> Self {
139 map_rest_error(source, |_| None)
140 }
141}
142
143impl From<rest::Error<rest::types::ApiError>> for RestClientError {
144 fn from(source: rest::Error<rest::types::ApiError>) -> Self {
145 map_rest_error(source, |api_error| {
146 Some((api_error.error_code, api_error.error_message))
147 })
148 }
149}