boatramp-types 0.2.2

Shared, wasm-clean wire types + routing/config logic for boatramp (used by the server, CLI, and the edge Worker so the wire format and routing can't drift)
Documentation
//! Wire DTOs for the captured-guest-logs endpoint: one bounded ring of recent
//! stdout/stderr lines plus the per-site rate-cap drop count. The server
//! captures and serializes these; the operator endpoint and the console tail
//! read them back.

use serde::{Deserialize, Serialize};

/// One captured guest log line, as the logs endpoint returns it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogEntry {
    /// Process-global monotonic sequence (a stable cursor for `--follow`).
    pub seq: u64,
    /// Capture time (Unix milliseconds).
    pub ts_ms: u64,
    /// Which stream it came from (`stdout` / `stderr`).
    pub stream: String,
    /// The line text (newline stripped).
    pub line: String,
}

/// The logs endpoint response: recent captured lines + the rate-cap drop count.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogsResponse {
    /// The captured lines (most recent `limit`, with `seq > after`).
    pub entries: Vec<LogEntry>,
    /// Lines dropped server-side by the per-site rate cap.
    pub dropped: u64,
}

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

    #[test]
    fn wire_shape_is_stable() {
        // The exact keys the server emits and the CLI/console read; pinned so
        // moving the DTO here can never silently rename a field.
        let entry = LogEntry {
            seq: 7,
            ts_ms: 1_700_000_000_000,
            stream: "stdout".into(),
            line: "hello".into(),
        };
        assert_eq!(
            serde_json::to_value(&entry).unwrap(),
            serde_json::json!({
                "seq": 7,
                "ts_ms": 1_700_000_000_000_u64,
                "stream": "stdout",
                "line": "hello",
            })
        );
        let resp = LogsResponse {
            entries: vec![entry.clone()],
            dropped: 3,
        };
        assert_eq!(
            serde_json::to_value(&resp).unwrap(),
            serde_json::json!({ "entries": [serde_json::to_value(&entry).unwrap()], "dropped": 3 })
        );
        // Readers that drop `ts_ms` still round-trip since the server always emits it.
        let back: LogEntry = serde_json::from_value(serde_json::to_value(&entry).unwrap()).unwrap();
        assert_eq!(back, entry);
    }
}