Skip to main content

dockworker/
response.rs

1///! Response from Dockerd
2///!
3use serde::{Deserialize, Serialize};
4use serde_json::value as json;
5use std::error::Error as StdError;
6use std::fmt;
7
8#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
9pub struct ProgressDetail {
10    pub current: u64,
11    pub total: u64,
12}
13
14#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
15#[allow(non_snake_case)]
16pub struct Progress {
17    /// image tag or hash of image layer or ...
18    pub id: String,
19    /// progress bar
20    pub progress: Option<String>,
21    /// progress detail
22    #[serde(deserialize_with = "progress_detail_opt::deserialize")]
23    pub progressDetail: Option<ProgressDetail>,
24    /// message or auxiliary info
25    pub status: String,
26}
27
28#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
29pub struct Stream {
30    pub stream: String,
31}
32
33#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
34pub struct Status {
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub id: Option<String>,
37    pub status: String,
38}
39
40#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
41#[allow(non_snake_case)]
42pub struct LogID {
43    pub ID: String,
44}
45
46#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
47pub struct Aux {
48    pub aux: LogID,
49}
50
51#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
52pub struct LogResponse {
53    pub response: String,
54}
55
56#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
57pub struct ErrorDetail {
58    pub message: String,
59}
60
61#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)]
62#[allow(non_snake_case)]
63pub struct Error {
64    pub error: String,
65    pub errorDetail: ErrorDetail,
66}
67
68/// Response of /images/create or other API
69///
70/// ## NOTE
71/// Structure of this type is not documented officialy.
72#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum Response {
75    Progress(Progress),
76    Status(Status),
77    Stream(Stream),
78    Aux(Aux),
79    Response(LogResponse),
80    Error(Error),
81    /// unknown response
82    Unknown(json::Value),
83}
84
85impl fmt::Display for Error {
86    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
87        write!(fmt, "{}: {}", self.error, self.errorDetail.message)
88    }
89}
90
91impl StdError for Error {
92    fn description(&self) -> &str {
93        &self.error
94    }
95
96    fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> {
97        None
98    }
99}
100
101impl Response {
102    pub fn as_error(&self) -> Option<&Error> {
103        use self::Response::*;
104        if let Error(err) = self {
105            Some(err)
106        } else {
107            None
108        }
109    }
110}
111
112mod progress_detail_opt {
113    use super::*;
114    use serde::de::{self, Deserializer, MapAccess, Visitor};
115
116    pub fn deserialize<'de, D>(de: D) -> Result<Option<ProgressDetail>, D::Error>
117    where
118        D: Deserializer<'de>,
119    {
120        struct OptVisitor;
121
122        impl<'de> Visitor<'de> for OptVisitor {
123            type Value = Option<ProgressDetail>;
124
125            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
126                formatter.write_str("Option<ProgressDetail>")
127            }
128
129            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
130            where
131                V: MapAccess<'de>,
132            {
133                let mut current = None;
134                let mut total = None;
135
136                match map.next_key()? {
137                    Some(mut key) => loop {
138                        match key {
139                            "current" => {
140                                if current.is_some() {
141                                    return Err(de::Error::duplicate_field("current"));
142                                }
143                                current = Some(map.next_value()?);
144                            }
145                            "total" => {
146                                if total.is_some() {
147                                    return Err(de::Error::duplicate_field("total"));
148                                }
149                                total = Some(map.next_value()?);
150                            }
151                            _ => return Err(de::Error::unknown_field(key, FIELDS)),
152                        };
153                        if let Some(key_) = map.next_key()? {
154                            key = key_;
155                        } else {
156                            break;
157                        }
158                    },
159                    None => return Ok(None), // {}
160                }
161
162                let current = current.ok_or_else(|| de::Error::missing_field("current"))?;
163                let total = total.ok_or_else(|| de::Error::missing_field("total"))?;
164
165                Ok(Some(ProgressDetail { current, total }))
166            }
167        }
168
169        const FIELDS: &[&str] = &["current", "total"];
170        de.deserialize_map(OptVisitor)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use self::Response as R;
177    use super::*;
178    use serde_json;
179
180    #[test]
181    #[rustfmt::skip]
182    fn progress() {
183        let s = r#"{
184            "status": "Downloading",
185            "progressDetail":{
186                "current":1596117,
187                "total":86451485
188            },
189            "progress":"[\u003e                                                  ]  1.596MB/86.45MB","id":"66aa7ce9b58b"
190        }"#;
191        assert_eq!(
192            R::Progress(Progress {
193                id: "66aa7ce9b58b".to_owned(),
194                progress:
195                    "[\u{003e}                                                  ]  1.596MB/86.45MB"
196                        .to_owned()
197                        .into(),
198                status: "Downloading".to_owned(),
199                progressDetail: Some(ProgressDetail {
200                    current: 1596117,
201                    total: 86451485,
202                }),
203            }),
204            serde_json::from_str(s).unwrap()
205        )
206    }
207
208    #[test]
209    fn progress_empty() {
210        let s = r#"{"status":"Already exists","progressDetail":{},"id":"18b8eb7e7f01"}"#;
211        assert_eq!(
212            Progress {
213                id: "18b8eb7e7f01".to_owned(),
214                progress: None,
215                progressDetail: None,
216                status: "Already exists".to_owned(),
217            },
218            serde_json::from_str(s).unwrap()
219        );
220    }
221
222    #[test]
223    fn status() {
224        let s = r#"{"status":"Pulling from eldesh/smlnj","id":"110.78"}"#;
225        assert_eq!(
226            R::Status(Status {
227                id: Some("110.78".to_owned()),
228                status: "Pulling from eldesh/smlnj".to_owned(),
229            }),
230            serde_json::from_str(s).unwrap()
231        )
232    }
233
234    #[test]
235    #[rustfmt::skip]
236    fn error() {
237        let s = r#"{
238            "errorDetail":{
239                "message":"failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device"
240            },
241            "error":"failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device"
242        }"#;
243        assert_eq!(
244            R::Error(Error {
245                error: "failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device".to_owned(),
246                errorDetail: ErrorDetail {
247                    message: "failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device".to_owned(),
248                },
249            }),
250            serde_json::from_str(s).unwrap()
251        )
252    }
253}