kitty_graphics_protocol/
response.rs1use crate::error::{Error, Result};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Response {
8 pub image_id: Option<u32>,
10 pub image_number: Option<u32>,
12 pub placement_id: Option<u32>,
14 pub success: bool,
16 pub error: Option<String>,
18}
19
20impl Response {
21 pub fn parse(data: &[u8]) -> Result<Self> {
23 if data.len() < 6 {
29 return Err(Error::InvalidResponse(
30 String::from_utf8_lossy(data).into_owned(),
31 ));
32 }
33
34 if data[0] != crate::ESC || data[1] != b'_' || data[2] != b'G' {
35 return Err(Error::InvalidResponse(
36 String::from_utf8_lossy(data).into_owned(),
37 ));
38 }
39
40 let semicolon_pos = data
42 .iter()
43 .position(|&b| b == b';')
44 .ok_or_else(|| Error::InvalidResponse(String::from_utf8_lossy(data).into_owned()))?;
45
46 let control = &data[3..semicolon_pos];
48 let control_str = std::str::from_utf8(control).map_err(Error::from)?;
49
50 let end_pos = data
52 .iter()
53 .rposition(|&b| b == crate::ESC)
54 .ok_or_else(|| Error::InvalidResponse(String::from_utf8_lossy(data).into_owned()))?;
55
56 let message = &data[semicolon_pos + 1..end_pos];
57 let message_str = std::str::from_utf8(message).map_err(Error::from)?;
58
59 let mut image_id = None;
61 let mut image_number = None;
62 let mut placement_id = None;
63
64 for part in control_str.split(',') {
65 let parts: Vec<&str> = part.splitn(2, '=').collect();
66 if parts.len() == 2 {
67 match parts[0] {
68 "i" => image_id = parts[1].parse().ok(),
69 "I" => image_number = parts[1].parse().ok(),
70 "p" => placement_id = parts[1].parse().ok(),
71 _ => {}
72 }
73 }
74 }
75
76 let (success, error) = if message_str == "OK" {
78 (true, None)
79 } else if let Some(err_msg) = message_str.strip_prefix("ENOENT:") {
80 (false, Some(format!("Not found: {err_msg}")))
81 } else if let Some(err_msg) = message_str.strip_prefix("EINVAL:") {
82 (false, Some(format!("Invalid argument: {err_msg}")))
83 } else if let Some(err_msg) = message_str.strip_prefix("EIO:") {
84 (false, Some(format!("IO error: {err_msg}")))
85 } else if let Some(err_msg) = message_str.strip_prefix("ETOODEEP:") {
86 (false, Some(format!("Chain too deep: {err_msg}")))
87 } else if let Some(err_msg) = message_str.strip_prefix("ECYCLE:") {
88 (false, Some(format!("Cycle detected: {err_msg}")))
89 } else if let Some(err_msg) = message_str.strip_prefix("ENOPARENT:") {
90 (false, Some(format!("Parent not found: {err_msg}")))
91 } else {
92 (false, Some(message_str.to_string()))
93 };
94
95 Ok(Response {
96 image_id,
97 image_number,
98 placement_id,
99 success,
100 error,
101 })
102 }
103
104 pub fn is_ok(&self) -> bool {
106 self.success
107 }
108
109 pub fn is_error(&self) -> bool {
111 !self.success
112 }
113
114 pub fn error_message(&self) -> Option<&str> {
116 self.error.as_deref()
117 }
118}
119
120impl std::fmt::Display for Response {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 if self.success {
123 write!(f, "OK")?;
124 if let Some(id) = self.image_id {
125 write!(f, " (image_id={}", id)?;
126 if let Some(pid) = self.placement_id {
127 write!(f, ", placement_id={}", pid)?;
128 }
129 write!(f, ")")?;
130 }
131 } else if let Some(err) = &self.error {
132 write!(f, "ERROR: {}", err)?;
133 }
134 Ok(())
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
140pub enum ErrorCode {
141 NotFound,
143 InvalidArgument,
145 IoError,
147 TooDeep,
149 Cycle,
151 NoParent,
153 Unknown,
155}
156
157impl ErrorCode {
158 pub fn from_message(msg: &str) -> Self {
160 if msg.starts_with("ENOENT") {
161 Self::NotFound
162 } else if msg.starts_with("EINVAL") {
163 Self::InvalidArgument
164 } else if msg.starts_with("EIO") {
165 Self::IoError
166 } else if msg.starts_with("ETOODEEP") {
167 Self::TooDeep
168 } else if msg.starts_with("ECYCLE") {
169 Self::Cycle
170 } else if msg.starts_with("ENOPARENT") {
171 Self::NoParent
172 } else {
173 Self::Unknown
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_parse_ok_response() {
184 let data = b"\x1b_Gi=42;OK\x1b\\";
185 let resp = Response::parse(data).unwrap();
186 assert!(resp.is_ok());
187 assert_eq!(resp.image_id, Some(42));
188 }
189
190 #[test]
191 fn test_parse_ok_response_with_placement() {
192 let data = b"\x1b_Gi=42,p=7;OK\x1b\\";
193 let resp = Response::parse(data).unwrap();
194 assert!(resp.is_ok());
195 assert_eq!(resp.image_id, Some(42));
196 assert_eq!(resp.placement_id, Some(7));
197 }
198
199 #[test]
200 fn test_parse_error_response() {
201 let data = b"\x1b_Gi=42;ENOENT:Image not found\x1b\\";
202 let resp = Response::parse(data).unwrap();
203 assert!(resp.is_error());
204 assert_eq!(resp.image_id, Some(42));
205 assert!(resp.error.unwrap().contains("Not found"));
206 }
207
208 #[test]
209 fn test_parse_response_with_image_number() {
210 let data = b"\x1b_Gi=99,I=13;OK\x1b\\";
211 let resp = Response::parse(data).unwrap();
212 assert!(resp.is_ok());
213 assert_eq!(resp.image_id, Some(99));
214 assert_eq!(resp.image_number, Some(13));
215 }
216}