Skip to main content

waos_dev_protocol/
messages.rs

1//! What `cargo waos` says to a WaOS, and what a WaOS says back, about one run; and what `waos
2//! open` says, files to open with a program, and the answer.
3//!
4//! The invariant: a variant's place and a field's place is its encoding (the crate's doc). A
5//! program's output is bytes, not lines, so a terminal shows what the program wrote as it wrote
6//! it, a prompt without its newline among it. Bytes are encoded as a string of bytes
7//! (`serde_bytes`), which postcard writes as it writes a `Vec<u8>`, its length and then its
8//! bytes, and reads in one copy rather than one call a byte: a debug build otherwise took about
9//! 0.2 s to send forever's 7 MB.
10
11use std::fmt;
12use std::path::PathBuf;
13
14use serde::{Deserialize, Serialize};
15use waos_package::ProgramId;
16
17/// What `cargo waos` says to a WaOS.
18#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub enum ToWaOS {
20    /// Run this component as the program `id` names, from its `[package.metadata.waos]`, with
21    /// that program's settings and folders, installed or not; the program running under that
22    /// id is stopped first.
23    Run {
24        id: ProgramId,
25        #[serde(with = "serde_bytes")]
26        component: Vec<u8>,
27    },
28    /// Open these files, absolute paths on this machine, with the program `id` names, as its
29    /// user would open them with it: the program running under that id, or the installed one,
30    /// started. Answered `Opened` or `Refused`, after which the connection ends.
31    Open { id: ProgramId, files: Vec<PathBuf> },
32}
33
34/// What a WaOS says back, about the run or the opening the connection asked for.
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub enum FromWaOS {
37    Started,
38    /// Bytes the program wrote, as it wrote them: a terminal shows them as they come.
39    Output {
40        stream: OutputStream,
41        #[serde(with = "serde_bytes")]
42        bytes: Vec<u8>,
43    },
44    Ended(RunEnd),
45    /// It could not be loaded or run, or the files could not be opened with it, and why.
46    Refused(String),
47    /// The files were handed to the program, which hears them once it listens.
48    Opened,
49}
50
51/// Which of a program's two output streams some bytes came from.
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
53pub enum OutputStream {
54    Stdout,
55    Stderr,
56}
57
58/// How the run ended.
59#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub enum RunEnd {
61    /// Its `main` returned; `success` is false for a run that reported failure.
62    Returned { success: bool },
63    /// It trapped, or could not run: the error.
64    Failed(String),
65    /// WaOS stopped it, as when the app quits.
66    Stopped,
67    /// A later run of the same name took its place.
68    Replaced,
69}
70
71/// A component is megabytes: an event that prints the message says how many, not which.
72impl fmt::Debug for ToWaOS {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match self {
75            ToWaOS::Run { id, component } => f
76                .debug_struct("Run")
77                .field("id", &format_args!("{id}"))
78                .field("component", &format_args!("{} bytes", component.len()))
79                .finish(),
80            ToWaOS::Open { id, files } => f
81                .debug_struct("Open")
82                .field("id", &format_args!("{id}"))
83                .field("files", files)
84                .finish(),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    /// The messages as they were encoded before their bytes were a string of bytes: each byte an
94    /// element of a sequence.
95    #[derive(Debug, PartialEq, Serialize, Deserialize)]
96    enum ToWaOSAsSequence {
97        Run { id: ProgramId, component: Vec<u8> },
98    }
99
100    #[derive(Debug, PartialEq, Serialize, Deserialize)]
101    enum FromWaOSAsSequence {
102        Started,
103        Output {
104            stream: OutputStream,
105            bytes: Vec<u8>,
106        },
107    }
108
109    /// Every byte, more than once, so the length takes more than one byte of its own.
110    fn bytes() -> Vec<u8> {
111        (0..=u8::MAX).cycle().take(1000).collect()
112    }
113
114    #[test]
115    fn a_run_is_encoded_as_it_was_when_its_component_was_a_sequence() {
116        let id: ProgramId = "dev.example.triangle".parse().unwrap();
117        let run = ToWaOS::Run {
118            id: id.clone(),
119            component: bytes(),
120        };
121        let as_sequence = ToWaOSAsSequence::Run {
122            id,
123            component: bytes(),
124        };
125        let encoded = postcard::to_allocvec(&run).unwrap();
126        assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
127        assert_eq!(
128            postcard::from_bytes::<ToWaOSAsSequence>(&encoded).unwrap(),
129            as_sequence
130        );
131    }
132
133    #[test]
134    fn output_is_encoded_as_it_was_when_its_bytes_were_a_sequence() {
135        let output = FromWaOS::Output {
136            stream: OutputStream::Stderr,
137            bytes: bytes(),
138        };
139        let as_sequence = FromWaOSAsSequence::Output {
140            stream: OutputStream::Stderr,
141            bytes: bytes(),
142        };
143        let encoded = postcard::to_allocvec(&output).unwrap();
144        assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
145        assert_eq!(postcard::from_bytes::<FromWaOS>(&encoded).unwrap(), output);
146    }
147
148    #[test]
149    fn an_opening_reads_back_as_written_and_prints_its_files() {
150        let open = ToWaOS::Open {
151            id: "dev.example.notes".parse().unwrap(),
152            files: vec![PathBuf::from("/Users/me/notes.md")],
153        };
154        let encoded = postcard::to_allocvec(&open).unwrap();
155        assert_eq!(postcard::from_bytes::<ToWaOS>(&encoded).unwrap(), open);
156        assert_eq!(
157            format!("{open:?}"),
158            "Open { id: dev.example.notes, files: [\"/Users/me/notes.md\"] }"
159        );
160        let opened = postcard::to_allocvec(&FromWaOS::Opened).unwrap();
161        assert_eq!(
162            postcard::from_bytes::<FromWaOS>(&opened).unwrap(),
163            FromWaOS::Opened
164        );
165    }
166
167    #[test]
168    fn a_run_prints_its_component_as_a_count() {
169        let run = ToWaOS::Run {
170            id: "dev.example.triangle".parse().unwrap(),
171            component: vec![0; 1024],
172        };
173        assert_eq!(
174            format!("{run:?}"),
175            "Run { id: dev.example.triangle, component: 1024 bytes }"
176        );
177    }
178}