waos-dev-protocol 0.1.0

What cargo waos and a running WaOS say to each other: the messages of one run, their frames, and the Unix socket that carries them.
Documentation
//! What `cargo waos` says to a WaOS, and what a WaOS says back, about one run; and what `waos
//! open` says, files to open with a program, and the answer.
//!
//! The invariant: a variant's place and a field's place is its encoding (the crate's doc). A
//! program's output is bytes, not lines, so a terminal shows what the program wrote as it wrote
//! it, a prompt without its newline among it. Bytes are encoded as a string of bytes
//! (`serde_bytes`), which postcard writes as it writes a `Vec<u8>`, its length and then its
//! bytes, and reads in one copy rather than one call a byte: a debug build otherwise took about
//! 0.2 s to send forever's 7 MB.

use std::fmt;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use waos_package::ProgramId;

/// What `cargo waos` says to a WaOS.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ToWaOS {
    /// Run this component as the program `id` names, from its `[package.metadata.waos]`, with
    /// that program's settings and folders, installed or not; the program running under that
    /// id is stopped first.
    Run {
        id: ProgramId,
        #[serde(with = "serde_bytes")]
        component: Vec<u8>,
    },
    /// Open these files, absolute paths on this machine, with the program `id` names, as its
    /// user would open them with it: the program running under that id, or the installed one,
    /// started. Answered `Opened` or `Refused`, after which the connection ends.
    Open { id: ProgramId, files: Vec<PathBuf> },
}

/// What a WaOS says back, about the run or the opening the connection asked for.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FromWaOS {
    Started,
    /// Bytes the program wrote, as it wrote them: a terminal shows them as they come.
    Output {
        stream: OutputStream,
        #[serde(with = "serde_bytes")]
        bytes: Vec<u8>,
    },
    Ended(RunEnd),
    /// It could not be loaded or run, or the files could not be opened with it, and why.
    Refused(String),
    /// The files were handed to the program, which hears them once it listens.
    Opened,
}

/// Which of a program's two output streams some bytes came from.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum OutputStream {
    Stdout,
    Stderr,
}

/// How the run ended.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RunEnd {
    /// Its `main` returned; `success` is false for a run that reported failure.
    Returned { success: bool },
    /// It trapped, or could not run: the error.
    Failed(String),
    /// WaOS stopped it, as when the app quits.
    Stopped,
    /// A later run of the same name took its place.
    Replaced,
}

/// A component is megabytes: an event that prints the message says how many, not which.
impl fmt::Debug for ToWaOS {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ToWaOS::Run { id, component } => f
                .debug_struct("Run")
                .field("id", &format_args!("{id}"))
                .field("component", &format_args!("{} bytes", component.len()))
                .finish(),
            ToWaOS::Open { id, files } => f
                .debug_struct("Open")
                .field("id", &format_args!("{id}"))
                .field("files", files)
                .finish(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The messages as they were encoded before their bytes were a string of bytes: each byte an
    /// element of a sequence.
    #[derive(Debug, PartialEq, Serialize, Deserialize)]
    enum ToWaOSAsSequence {
        Run { id: ProgramId, component: Vec<u8> },
    }

    #[derive(Debug, PartialEq, Serialize, Deserialize)]
    enum FromWaOSAsSequence {
        Started,
        Output {
            stream: OutputStream,
            bytes: Vec<u8>,
        },
    }

    /// Every byte, more than once, so the length takes more than one byte of its own.
    fn bytes() -> Vec<u8> {
        (0..=u8::MAX).cycle().take(1000).collect()
    }

    #[test]
    fn a_run_is_encoded_as_it_was_when_its_component_was_a_sequence() {
        let id: ProgramId = "dev.example.triangle".parse().unwrap();
        let run = ToWaOS::Run {
            id: id.clone(),
            component: bytes(),
        };
        let as_sequence = ToWaOSAsSequence::Run {
            id,
            component: bytes(),
        };
        let encoded = postcard::to_allocvec(&run).unwrap();
        assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
        assert_eq!(
            postcard::from_bytes::<ToWaOSAsSequence>(&encoded).unwrap(),
            as_sequence
        );
    }

    #[test]
    fn output_is_encoded_as_it_was_when_its_bytes_were_a_sequence() {
        let output = FromWaOS::Output {
            stream: OutputStream::Stderr,
            bytes: bytes(),
        };
        let as_sequence = FromWaOSAsSequence::Output {
            stream: OutputStream::Stderr,
            bytes: bytes(),
        };
        let encoded = postcard::to_allocvec(&output).unwrap();
        assert_eq!(encoded, postcard::to_allocvec(&as_sequence).unwrap());
        assert_eq!(postcard::from_bytes::<FromWaOS>(&encoded).unwrap(), output);
    }

    #[test]
    fn an_opening_reads_back_as_written_and_prints_its_files() {
        let open = ToWaOS::Open {
            id: "dev.example.notes".parse().unwrap(),
            files: vec![PathBuf::from("/Users/me/notes.md")],
        };
        let encoded = postcard::to_allocvec(&open).unwrap();
        assert_eq!(postcard::from_bytes::<ToWaOS>(&encoded).unwrap(), open);
        assert_eq!(
            format!("{open:?}"),
            "Open { id: dev.example.notes, files: [\"/Users/me/notes.md\"] }"
        );
        let opened = postcard::to_allocvec(&FromWaOS::Opened).unwrap();
        assert_eq!(
            postcard::from_bytes::<FromWaOS>(&opened).unwrap(),
            FromWaOS::Opened
        );
    }

    #[test]
    fn a_run_prints_its_component_as_a_count() {
        let run = ToWaOS::Run {
            id: "dev.example.triangle".parse().unwrap(),
            component: vec![0; 1024],
        };
        assert_eq!(
            format!("{run:?}"),
            "Run { id: dev.example.triangle, component: 1024 bytes }"
        );
    }
}