feat(proto): envelope, commands, events, ids with serde round-trip tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,115 @@
|
||||
// populated in Task 1
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::ids::{SurfaceId, WorkspaceId};
|
||||
|
||||
/// Wire envelope. `kind` is the serde tag.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum Envelope {
|
||||
Req {
|
||||
id: u64,
|
||||
cmd: Cmd,
|
||||
},
|
||||
Res {
|
||||
id: u64,
|
||||
ok: bool,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
data: serde_json::Value,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
error: Option<ErrorBody>,
|
||||
},
|
||||
Evt(Evt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ErrorBody {
|
||||
pub code: String,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
/// Client → daemon commands. The active subset for M0+M1.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "cmd", content = "args", rename_all = "snake_case")]
|
||||
pub enum Cmd {
|
||||
Open { path: String },
|
||||
NewSurface {
|
||||
workspace_id: WorkspaceId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
command: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
args: Vec<String>,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
},
|
||||
Input {
|
||||
surface_id: SurfaceId,
|
||||
/// base64-encoded keyboard bytes.
|
||||
bytes: String,
|
||||
},
|
||||
Resize { surface_id: SurfaceId, cols: u16, rows: u16 },
|
||||
Attach { surface_id: SurfaceId },
|
||||
Detach { surface_id: SurfaceId },
|
||||
Focus { surface_id: SurfaceId },
|
||||
Close { surface_id: SurfaceId },
|
||||
Status,
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
/// Daemon → subscribers push events. The active subset for M0+M1.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "evt", content = "data", rename_all = "snake_case")]
|
||||
pub enum Evt {
|
||||
Output { surface_id: SurfaceId, bytes: Vec<u8> },
|
||||
Exit { surface_id: SurfaceId, code: i32 },
|
||||
SurfaceCreated { surface_id: SurfaceId, workspace_id: WorkspaceId },
|
||||
SurfaceClosed { surface_id: SurfaceId },
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ids::{SurfaceId, WorkspaceId};
|
||||
|
||||
#[test]
|
||||
fn req_round_trips_through_json() {
|
||||
let env = Envelope::Req {
|
||||
id: 42,
|
||||
cmd: Cmd::Focus { surface_id: SurfaceId("s_8f3".into()) },
|
||||
};
|
||||
let json = serde_json::to_string(&env).unwrap();
|
||||
let back: Envelope = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(env, back);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_ok_and_err_serialize_distinctly() {
|
||||
let ok = Envelope::Res { id: 1, ok: true, data: serde_json::json!({"workspace_id":"w_1"}), error: None };
|
||||
let err = Envelope::Res { id: 2, ok: false, data: serde_json::Value::Null,
|
||||
error: Some(ErrorBody { code: "NOT_FOUND".into(), msg: "no surface".into() }) };
|
||||
assert!(serde_json::to_string(&ok).unwrap().contains("\"ok\":true"));
|
||||
assert!(serde_json::to_string(&err).unwrap().contains("NOT_FOUND"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evt_output_carries_workspace_scoped_surface() {
|
||||
let evt = Envelope::Evt(Evt::Output {
|
||||
surface_id: SurfaceId("s_1".into()),
|
||||
bytes: vec![104, 105],
|
||||
});
|
||||
let json = serde_json::to_string(&evt).unwrap();
|
||||
let back: Envelope = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(evt, back);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_surface_defaults_cmd_to_none() {
|
||||
let json = r#"{"kind":"req","id":7,"cmd":{"cmd":"new_surface","args":{"workspace_id":"w_1","cols":80,"rows":24}}}"#;
|
||||
let env: Envelope = serde_json::from_str(json).unwrap();
|
||||
match env {
|
||||
Envelope::Req { cmd: Cmd::NewSurface { command, args, .. }, .. } => {
|
||||
assert!(command.is_none());
|
||||
assert!(args.is_empty());
|
||||
}
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user