1use std::fmt;
15use std::path::PathBuf;
16
17use serde::{Deserialize, Serialize};
18use waos_package::ProgramId;
19
20#[derive(Clone, PartialEq, Serialize, Deserialize)]
22pub enum ToWaOS {
23 Run {
27 id: ProgramId,
28 #[serde(with = "serde_bytes")]
29 component: Vec<u8>,
30 },
31 Open { id: ProgramId, files: Vec<PathBuf> },
35 Screenshot { id: ProgramId },
38 Input {
43 id: ProgramId,
44 window: u32,
45 input: Input,
46 },
47}
48
49#[derive(Clone, PartialEq, Serialize, Deserialize)]
52pub enum Input {
53 Tap { x: f64, y: f64 },
55 Text(String),
57 Key(String),
59 Scroll { x: f64, y: f64, dx: f64, dy: f64 },
62}
63
64#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
67pub enum FromWaOS {
68 Started,
69 Output {
71 stream: OutputStream,
72 #[serde(with = "serde_bytes")]
73 bytes: Vec<u8>,
74 },
75 Ended(RunEnd),
76 Refused(String),
78 Opened,
80 Screenshots(Vec<WindowPicture>),
82 InputDone {
86 new_picture: bool,
87 },
88 Unreached(Unreached),
90}
91
92#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct WindowPicture {
95 pub window: u32,
98 pub title: String,
101 pub width: u32,
104 pub height: u32,
105 pub in_sight: bool,
108 #[serde(with = "serde_bytes")]
111 pub png: Option<Vec<u8>>,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116pub enum Unreached {
117 NotRunByCargoWaos,
120 NoWindow { window: u32, windows: u32 },
122 OutsideWindow { width: u32, height: u32 },
124 NotEditing,
126 UnknownKey(String),
128}
129
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
132pub enum OutputStream {
133 Stdout,
134 Stderr,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
139pub enum RunEnd {
140 Returned { success: bool },
142 Failed(String),
144 Stopped,
146 Replaced,
148}
149
150impl fmt::Debug for ToWaOS {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 ToWaOS::Run { id, component } => f
156 .debug_struct("Run")
157 .field("id", &format_args!("{id}"))
158 .field("component", &format_args!("{} bytes", component.len()))
159 .finish(),
160 ToWaOS::Open { id, files } => f
161 .debug_struct("Open")
162 .field("id", &format_args!("{id}"))
163 .field("files", files)
164 .finish(),
165 ToWaOS::Screenshot { id } => f
166 .debug_struct("Screenshot")
167 .field("id", &format_args!("{id}"))
168 .finish(),
169 ToWaOS::Input { id, window, input } => f
170 .debug_struct("Input")
171 .field("id", &format_args!("{id}"))
172 .field("window", window)
173 .field("input", input)
174 .finish(),
175 }
176 }
177}
178
179impl fmt::Debug for Input {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 match self {
182 Input::Tap { x, y } => f.debug_struct("Tap").field("x", x).field("y", y).finish(),
183 Input::Text(text) => f
184 .debug_tuple("Text")
185 .field(&format_args!("{} characters", text.chars().count()))
186 .finish(),
187 Input::Key(key) => f.debug_tuple("Key").field(key).finish(),
188 Input::Scroll { x, y, dx, dy } => f
189 .debug_struct("Scroll")
190 .field("x", x)
191 .field("y", y)
192 .field("dx", dx)
193 .field("dy", dy)
194 .finish(),
195 }
196 }
197}
198
199impl fmt::Debug for WindowPicture {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.debug_struct("WindowPicture")
203 .field("window", &self.window)
204 .field("title", &self.title)
205 .field("width", &self.width)
206 .field("height", &self.height)
207 .field("in_sight", &self.in_sight)
208 .field("png_bytes", &self.png.as_ref().map(Vec::len))
209 .finish()
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[derive(Debug, PartialEq, Serialize, Deserialize)]
220 enum ToWaOSAsSequence {
221 Run { id: ProgramId, component: Vec<u8> },
222 }
223
224 #[derive(Debug, PartialEq, Serialize, Deserialize)]
225 enum FromWaOSAsSequence {
226 Started,
227 Output {
228 stream: OutputStream,
229 bytes: Vec<u8>,
230 },
231 }
232
233 fn bytes() -> Vec<u8> {
235 (0..=u8::MAX).cycle().take(1000).collect()
236 }
237
238 #[test]
239 fn a_run_is_encoded_as_it_was_when_its_component_was_a_sequence() {
240 let id: ProgramId = "dev.example.triangle".parse().unwrap();
241 let run = ToWaOS::Run {
242 id: id.clone(),
243 component: bytes(),
244 };
245 let as_sequence = ToWaOSAsSequence::Run {
246 id,
247 component: bytes(),
248 };
249 let encoded = postcard::to_allocvec(&run).unwrap();
250 assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
251 assert_eq!(
252 postcard::from_bytes::<ToWaOSAsSequence>(&encoded).unwrap(),
253 as_sequence
254 );
255 }
256
257 #[test]
258 fn output_is_encoded_as_it_was_when_its_bytes_were_a_sequence() {
259 let output = FromWaOS::Output {
260 stream: OutputStream::Stderr,
261 bytes: bytes(),
262 };
263 let as_sequence = FromWaOSAsSequence::Output {
264 stream: OutputStream::Stderr,
265 bytes: bytes(),
266 };
267 let encoded = postcard::to_allocvec(&output).unwrap();
268 assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
269 assert_eq!(postcard::from_bytes::<FromWaOS>(&encoded).unwrap(), output);
270 }
271
272 #[test]
273 fn an_opening_reads_back_as_written_and_prints_its_files() {
274 let open = ToWaOS::Open {
275 id: "dev.example.notes".parse().unwrap(),
276 files: vec![PathBuf::from("/Users/me/notes.md")],
277 };
278 let encoded = postcard::to_allocvec(&open).unwrap();
279 assert_eq!(postcard::from_bytes::<ToWaOS>(&encoded).unwrap(), open);
280 assert_eq!(
281 format!("{open:?}"),
282 "Open { id: dev.example.notes, files: [\"/Users/me/notes.md\"] }"
283 );
284 let opened = postcard::to_allocvec(&FromWaOS::Opened).unwrap();
285 assert_eq!(
286 postcard::from_bytes::<FromWaOS>(&opened).unwrap(),
287 FromWaOS::Opened
288 );
289 }
290
291 #[test]
292 fn a_run_prints_its_component_as_a_count() {
293 let run = ToWaOS::Run {
294 id: "dev.example.triangle".parse().unwrap(),
295 component: vec![0; 1024],
296 };
297 assert_eq!(
298 format!("{run:?}"),
299 "Run { id: dev.example.triangle, component: 1024 bytes }"
300 );
301 }
302
303 #[test]
304 fn a_screenshot_and_each_input_read_back_as_written() {
305 let id: ProgramId = "dev.example.notes".parse().unwrap();
306 let inputs = [
307 Input::Tap { x: 120.5, y: 48.0 },
308 Input::Text("milk, eggs".into()),
309 Input::Key("cmd+s".into()),
310 Input::Scroll {
311 x: 10.0,
312 y: 20.0,
313 dx: 0.0,
314 dy: -200.0,
315 },
316 ];
317 let messages = std::iter::once(ToWaOS::Screenshot { id: id.clone() }).chain(
318 inputs.into_iter().map(|input| ToWaOS::Input {
319 id: id.clone(),
320 window: 2,
321 input,
322 }),
323 );
324 for message in messages {
325 let encoded = postcard::to_allocvec(&message).unwrap();
326 assert_eq!(postcard::from_bytes::<ToWaOS>(&encoded).unwrap(), message);
327 }
328 }
329
330 #[test]
331 fn typed_text_is_left_out_of_what_a_message_prints() {
332 let input = ToWaOS::Input {
333 id: "dev.example.notes".parse().unwrap(),
334 window: 1,
335 input: Input::Text("hunter2".into()),
336 };
337 let printed = format!("{input:?}");
338 assert!(!printed.contains("hunter2"), "{printed}");
339 assert!(printed.contains("7 characters"), "{printed}");
340 }
341
342 #[test]
343 fn a_picture_prints_its_png_as_a_count() {
344 let picture = WindowPicture {
345 window: 1,
346 title: "Notes".into(),
347 width: 1600,
348 height: 1200,
349 in_sight: true,
350 png: Some(vec![0; 2048]),
351 };
352 let printed = format!("{:?}", FromWaOS::Screenshots(vec![picture]));
353 assert!(printed.contains("png_bytes: Some(2048)"), "{printed}");
354 assert!(!printed.contains("0, 0"), "{printed}");
355 }
356}