1use 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 pub id: String,
19 pub progress: Option<String>,
21 #[serde(deserialize_with = "progress_detail_opt::deserialize")]
23 pub progressDetail: Option<ProgressDetail>,
24 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#[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(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), }
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}