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}