Skip to main content

spaceterm_proto/
message.rs

1//! TBP messages: the verbs a tool sends to the terminal.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::bundle::MimeBundle;
7use crate::tier::TrustTier;
8use crate::BlockId;
9
10// ============================================================================
11// Data Structures
12// ============================================================================
13
14/// A single TBP message, decoded from one OSC escape.
15#[derive(Clone, Debug, PartialEq)]
16pub enum Message {
17    /// Query terminal capabilities. The reply travels out of band (see
18    /// [`crate::Caps`]), not as another `Message`.
19    Caps,
20    /// Close a live block.
21    Close(BlockId),
22    /// Emit a one-shot block.
23    Emit(EmitBlock),
24    /// Open a live block for subsequent incremental updates.
25    Open(OpenBlock),
26    /// Apply an incremental update to a live block.
27    Patch(PatchBlock),
28}
29
30/// Payload of [`Message::Emit`]: a complete block rendered once.
31#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
32pub struct EmitBlock {
33    pub bundle: MimeBundle,
34    pub id: BlockId,
35    pub trust: TrustTier,
36}
37
38/// Payload of [`Message::Open`]: the initial state of a live block.
39#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
40pub struct OpenBlock {
41    pub id: BlockId,
42    pub mime: String,
43    pub spec: Value,
44}
45
46/// Payload of [`Message::Patch`]: an RFC 6902 JSON patch applied to a live block.
47#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
48pub struct PatchBlock {
49    pub id: BlockId,
50    pub patch: Value,
51}
52
53// ============================================================================
54// Tests
55// ============================================================================
56
57#[cfg(test)]
58mod tests {
59    use serde_json::Value;
60
61    use super::*;
62
63    #[test]
64    fn test_message_equality_distinguishes_variants() {
65        let mut bundle = MimeBundle::new();
66        bundle.insert("text/plain", Value::from("hi"));
67        let emit = Message::Emit(EmitBlock {
68            bundle,
69            id: BlockId(1),
70            trust: TrustTier::Restricted,
71        });
72        assert_ne!(emit, Message::Caps);
73        assert_ne!(emit, Message::Close(BlockId(1)));
74    }
75
76    #[test]
77    fn test_emit_block_fields_round_trip() {
78        let mut bundle = MimeBundle::new();
79        bundle.insert("text/plain", Value::from("fallback"));
80        let block = EmitBlock {
81            bundle: bundle.clone(),
82            id: BlockId(99),
83            trust: TrustTier::Trusted,
84        };
85        assert_eq!(block.id, BlockId(99));
86        assert_eq!(block.trust, TrustTier::Trusted);
87        assert_eq!(
88            block.bundle.get("text/plain"),
89            Some(&Value::from("fallback"))
90        );
91    }
92
93    #[test]
94    fn test_open_block_holds_spec() {
95        let spec = serde_json::json!({"mark": "bar"});
96        let block = OpenBlock {
97            id: BlockId(7),
98            mime: "application/vnd.vega-lite+json".to_string(),
99            spec: spec.clone(),
100        };
101        assert_eq!(block.id, BlockId(7));
102        assert_eq!(block.mime, "application/vnd.vega-lite+json");
103        assert_eq!(block.spec, spec);
104    }
105
106    #[test]
107    fn test_patch_block_holds_json_patch() {
108        let patch = serde_json::json!([{"op": "replace", "path": "/x", "value": 1}]);
109        let block = PatchBlock {
110            id: BlockId(3),
111            patch: patch.clone(),
112        };
113        assert_eq!(block.id, BlockId(3));
114        assert_eq!(block.patch, patch);
115    }
116}