Skip to main content

unifier/daemon/
protocol.rs

1//! Line-delimited JSON protocol between CLI clients and the hot daemon.
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::postbox::Message;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "op", rename_all = "snake_case")]
10pub enum Request {
11    Put {
12        key: String,
13        value: String,
14    },
15    Get {
16        key: String,
17    },
18    Del {
19        key: String,
20    },
21    Send {
22        #[serde(default)]
23        from: Option<String>,
24        recipient: String,
25        message: String,
26    },
27    Cron {
28        schedule: String,
29        message: String,
30    },
31    Poll {
32        recipient: String,
33    },
34    PollCron,
35    List {
36        path: String,
37    },
38    Ack {
39        id_or_path: String,
40    },
41    Flush,
42    Ping,
43    Shutdown,
44    TickStart {
45        label: String,
46    },
47    TickEnd,
48    TickStatus,
49    TickLock {
50        key: String,
51    },
52    TickUnlock {
53        key: String,
54    },
55    Event {
56        payload: String,
57        /// Seconds until the event is deleted. Omit to use the default (24h) or payload fields.
58        #[serde(default)]
59        ttl: Option<u64>,
60    },
61    AgentMessage {
62        from: String,
63        to: String,
64        payload: String,
65    },
66    /// Report HTTP base URL / port for the www server.
67    WebStatus,
68    /// List published temp files.
69    WebList,
70    /// Remove a published temp file by name.
71    WebRm {
72        name: String,
73    },
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(tag = "status", rename_all = "snake_case")]
78pub enum Response {
79    Ok {
80        #[serde(default, skip_serializing_if = "Option::is_none")]
81        value: Option<String>,
82        #[serde(default, skip_serializing_if = "Option::is_none")]
83        uuid: Option<Uuid>,
84        #[serde(default, skip_serializing_if = "Option::is_none")]
85        found: Option<bool>,
86        #[serde(default, skip_serializing_if = "Option::is_none")]
87        dirty: Option<bool>,
88        #[serde(default, skip_serializing_if = "Option::is_none")]
89        tick: Option<u64>,
90        #[serde(default, skip_serializing_if = "Option::is_none")]
91        queued: Option<usize>,
92        #[serde(default, skip_serializing_if = "Vec::is_empty")]
93        messages: Vec<MessageDto>,
94    },
95    Err {
96        error: String,
97    },
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct MessageDto {
102    pub id: Uuid,
103    pub path: String,
104    pub body: String,
105}
106
107impl From<Message> for MessageDto {
108    fn from(m: Message) -> Self {
109        Self {
110            id: m.id,
111            path: m.path.display().to_string(),
112            body: m.body,
113        }
114    }
115}
116
117impl From<MessageDto> for Message {
118    fn from(m: MessageDto) -> Self {
119        Self {
120            id: m.id,
121            path: m.path.into(),
122            body: m.body,
123        }
124    }
125}
126
127pub fn encode_response(response: &Response) -> Result<String, serde_json::Error> {
128    Ok(format!("{}\n", serde_json::to_string(response)?))
129}
130
131pub fn decode_request(line: &str) -> Result<Request, serde_json::Error> {
132    serde_json::from_str(line.trim())
133}
134
135pub fn decode_response(line: &str) -> Result<Response, serde_json::Error> {
136    serde_json::from_str(line.trim())
137}
138
139pub fn ok_empty() -> Response {
140    Response::Ok {
141        value: None,
142        uuid: None,
143        found: None,
144        dirty: None,
145        tick: None,
146        queued: None,
147        messages: vec![],
148    }
149}