Skip to main content

passivized_docker_engine_client/errors/
dec_use.rs

1use std::fmt::{Display, Formatter};
2use std::string::FromUtf8Error;
3
4use hyper::StatusCode;
5use crate::imp::api::DockerEngineApiBuilderError;
6
7use crate::errors::DecLibraryError;
8use crate::model::StreamLineReadError;
9use crate::imp::http_proxy::DockerEngineResponseNotUtf8;
10
11/// An error during the use of a Docker Engine client.
12#[derive(Debug)]
13pub enum DecUseError {
14
15    /// Received a 404 Not Found response for a list-based api.
16    ///
17    /// Possible causes are:
18    ///   1. DockerEngineClient was misconfigured with an incorrect URL.
19    ///   2. URL is correct but the Docker Engine it points to is incompatible with this library
20    ApiNotFound {
21        uri: String
22    },
23
24    /// Server returned 501 Not Implemented, most likely
25    ///
26    /// Possible causes are:
27    ///   1. DockerEngineClient was misconfigured with an incorrect URL.
28    ///   2. URL is correct but the Docker Engine it points to is incompatible with this library
29    ApiNotImplemented {
30        uri: String
31    },
32
33    /// A communication failure occurred while sending an HTTP request or receiving its response.
34    HttpClientError(hyper::Error),
35
36    /// See docs for DecInternalError.
37    Internal(DecLibraryError),
38
39    /// An item managed by the Docker Engine, and required by the request,
40    /// was not found (does not exist in the Docker Engine).
41    ///
42    /// For example, you requested to start a container, but that container
43    /// does not exist.
44    NotFound {
45        /// Error message returned by the Docker Engine
46        message: String
47    },
48
49    /// A problem while reading or parsing a container log or console output stream.
50    StreamLineRead(StreamLineReadError),
51
52    /// Docker Engine rejected the request. Any number of failure status
53    /// codes can produce this outcome, including but not limited to:
54    ///
55    /// * 400 Bad Request
56    /// * 409 Conflict
57    /// * 500 Server Error
58    ///
59    /// However, this will not be used for 404 Not Found responses.
60    Rejected {
61        /// HTTP status returned by the Docker Engine
62        status: StatusCode,
63
64        /// Error message returned by the Docker Engine
65        message: String
66    },
67
68    /// Received a response from the HTTP server with an unexpected or missing Content-Type.
69    UnexpectedResponseContentType {
70        expected: String,
71        actual: Option<String>
72    },
73
74    /// A response was received, but could not be parsed.
75    ///
76    /// The two most likely causes:
77    ///   1. The JSON was malformed (or not json at all)
78    ///   2. The JSON was well formed, but the structure did not match the client's expectations
79    ///
80    /// A reverse-proxy related failure can cause #1 above. Malformed JSON from the actual Docker Engine is highly unlikely.
81    ///
82    /// This enum is a possible outcome for both HTTP success statuses and failure statuses.
83    UnparseableJsonResponse {
84        /// HTTP status returned by the Docker Engine or whatever HTTP server we were connected to
85        status: StatusCode,
86
87        /// Raw text from HTTP response body. May or may not be valid JSON
88        text: String,
89
90        /// Error that prevent conversion to the expected response data structure
91        parse_error: serde_json::error::Error
92    },
93
94    /// A response was received, but could not be decoded as UTF-8.
95    ///
96    /// The most likely cause is a reverse-proxy related failure, and the
97    /// reverse proxy sending back a non-UTF-8 plain text error message.
98    /// Malformed UTF-8 from the actual Docker Engine is highly unlikely.
99    ///
100    /// This enum is a possible outcome for both HTTP success statuses and failure statuses.
101    UnparseableUtf8Response {
102        /// HTTP status returned by the Docker Engine or whatever HTTP server we were connected to
103        status: StatusCode,
104
105        /// HTTP Content-Type response header value returned by the Docker Engine or whatever HTTP server we were connected to
106        content_type: Option<String>,
107
108        /// Error that prevented converting the HTTP response body bytes into UTF-8 (the encoding of application/json).
109        parse_error: FromUtf8Error
110    },
111
112}
113
114impl DecUseError {
115
116    pub fn error_message(&self) -> String {
117        match self {
118            Self::ApiNotFound { uri } =>
119                format!("Api not found at {}", uri),
120
121            Self::ApiNotImplemented { uri } =>
122                format!("Api not implemented at {}", uri),
123
124            Self::Internal(internal) =>
125                internal.message(),
126
127            Self::NotFound { message } =>
128                message.clone(),
129
130            Self::Rejected { status, message } =>
131                format!("Request rejected with HTTP status: {}: {}", status, message),
132
133            Self::StreamLineRead(error) =>
134                error.error_message(),
135
136            Self::UnparseableJsonResponse { status, text, parse_error} =>
137                format!("Response with status {} had unparseable JSON: {}; response below:\n{}", status, parse_error, text),
138
139            Self::UnparseableUtf8Response { status, content_type, parse_error } =>
140                format!(
141                    "Response with status {} and {} not parseable as UTF-8: {}",
142                    status,
143                    match content_type {
144                        None => "no content type".into(),
145                        Some(ct) => format!("content type {}", ct)
146                    },
147                    parse_error
148                ),
149
150            Self::HttpClientError(hyper_error) =>
151                format!("Response error: {}", hyper_error),
152
153            Self::UnexpectedResponseContentType { expected, actual } =>
154                format!(
155                    "Expected response Content-Type of {} but {}",
156                    expected,
157                    match actual {
158                        None =>
159                            "header was missing".to_string(),
160
161                        Some(a) =>
162                            format!("received {}", a)
163                    }
164                )
165
166        }
167    }
168
169    // A more explicit conversion than a From/Into trait pair. Prevents misconversion of error status responses.
170    pub fn from_not_utf8(other: DockerEngineResponseNotUtf8) -> Self {
171        Self::UnparseableUtf8Response {
172            status: other.status,
173            content_type: other.content_type,
174            parse_error: other.error
175        }
176    }
177}
178
179impl Display for DecUseError {
180    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
181        write!(f, "{}", self.error_message())
182    }
183}
184
185impl From<DecLibraryError> for DecUseError {
186    fn from(other: DecLibraryError) -> Self {
187        Self::Internal(other)
188    }
189}
190
191impl From<DockerEngineApiBuilderError> for DecUseError {
192    fn from(other: DockerEngineApiBuilderError) -> Self {
193        DecLibraryError::from(other).into()
194    }
195}
196
197impl From<StreamLineReadError> for DecUseError {
198    fn from(other: StreamLineReadError) -> Self {
199        Self::StreamLineRead(other)
200    }
201}
202
203impl From<url::ParseError> for DecUseError {
204    fn from(other: url::ParseError) -> Self {
205        DecLibraryError::from(other).into()
206    }
207}
208
209#[cfg(test)]
210mod test_error_message_and_display {
211    use crate::errors::DecUseError;
212
213    #[test]
214    pub fn response_content_type_missing() {
215        let error = DecUseError::UnexpectedResponseContentType {
216            expected: "foo".into(),
217            actual: None
218        };
219
220        let actual = format!("{}", error);
221
222        assert_eq!("Expected response Content-Type of foo but header was missing".to_string(), actual);
223    }
224
225    #[test]
226    pub fn response_content_type_wrong() {
227        let error = DecUseError::UnexpectedResponseContentType {
228            expected: "bar".into(),
229            actual: Some("qux".into())
230        };
231
232        let actual = format!("{}", error);
233
234        assert_eq!("Expected response Content-Type of bar but received qux".to_string(), actual);
235    }
236}