Skip to main content

xplane_web_api/
error.rs

1//! REST client error classification helpers.
2
3use thiserror::Error;
4
5use crate::rest;
6
7/// Unified error type for generated REST client calls.
8#[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}