Skip to main content

stackless_daemon/
rpc.rs

1//! The daemon RPC protocol: JSON lines over the unix socket, one
2//! request line, one response line. Every exchange carries the
3//! sender's version so the upgrade handshake (§3) is just a field
4//! compare — a newer CLI tells an older daemon to drain and exit.
5
6use serde::{Deserialize, Serialize};
7use stackless_core::types::{DnsName, Pid, ProcessStartTime, ProtocolVersion, ProxyHost, TcpPort};
8
9pub const PROTOCOL_VERSION: u32 = 1;
10
11/// The daemon's (and CLI's) build version — same binary, same number.
12pub fn build_version() -> &'static str {
13    env!("CARGO_PKG_VERSION")
14}
15
16#[derive(Debug, Serialize, Deserialize)]
17#[serde(tag = "cmd", rename_all = "kebab-case")]
18pub enum Request {
19    Ping,
20    /// Route `host` (no port) to a local TCP port.
21    RouteSet {
22        host: ProxyHost,
23        port: TcpPort,
24    },
25    RouteDelete {
26        host: ProxyHost,
27    },
28    Routes,
29    /// Record a service process for supervision (PID-reuse-safe).
30    Supervise {
31        instance: DnsName,
32        service: DnsName,
33        pid: Pid,
34        start_time: ProcessStartTime,
35    },
36    /// Forget one instance's supervision records and routes.
37    Forget {
38        instance: DnsName,
39    },
40    /// The instance's supervised processes, observed live/dead now.
41    InstanceProcesses {
42        instance: DnsName,
43    },
44    /// Drain and exit (the upgrade handshake).
45    Shutdown,
46}
47
48#[derive(Debug, Serialize, Deserialize)]
49pub struct Envelope<T> {
50    pub protocol: ProtocolVersion,
51    pub version: String,
52    #[serde(flatten)]
53    pub body: T,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
57#[serde(untagged)]
58pub enum Response {
59    Ok(ResponseBody),
60    Err { error: String },
61}
62
63#[derive(Debug, Serialize, Deserialize)]
64#[serde(tag = "result", rename_all = "kebab-case")]
65pub enum ResponseBody {
66    Pong,
67    Done,
68    Routes { routes: Vec<Route> },
69    Processes { processes: Vec<SupervisedProcess> },
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct Route {
74    pub host: ProxyHost,
75    pub port: TcpPort,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct SupervisedProcess {
80    pub instance: DnsName,
81    pub service: DnsName,
82    pub pid: Pid,
83    pub start_time: ProcessStartTime,
84    pub alive: bool,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn route_roundtrip() {
93        let original = Route {
94            host: ProxyHost::try_new("api.dev.localhost").unwrap(),
95            port: TcpPort::try_new(8080).unwrap(),
96        };
97        let json = serde_json::to_string(&original).unwrap();
98        let restored: Route = serde_json::from_str(&json).unwrap();
99        assert_eq!(original, restored);
100    }
101}