Skip to main content

fips_core/control/
protocol.rs

1//! Control socket protocol types.
2//!
3//! Line-delimited JSON protocol for the Unix domain socket.
4//! Each request is one JSON line, each response is one JSON line.
5
6use serde::{Deserialize, Serialize};
7
8/// A control request from a client.
9#[derive(Debug, Deserialize)]
10pub struct Request {
11    /// The command to execute (e.g., "show_status", "connect").
12    pub command: String,
13    /// Optional parameters for mutating commands.
14    #[serde(default)]
15    pub params: Option<serde_json::Value>,
16}
17
18/// A control response to a client.
19#[derive(Debug, Serialize)]
20pub struct Response {
21    /// "ok" or "error".
22    pub status: String,
23    /// Response data (present on success).
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub data: Option<serde_json::Value>,
26    /// Error message (present on failure).
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub message: Option<String>,
29}
30
31impl Response {
32    /// Create a success response with data.
33    pub fn ok(data: serde_json::Value) -> Self {
34        Self {
35            status: "ok".to_string(),
36            data: Some(data),
37            message: None,
38        }
39    }
40
41    /// Create an error response with a message.
42    pub fn error(message: impl Into<String>) -> Self {
43        Self {
44            status: "error".to_string(),
45            data: None,
46            message: Some(message.into()),
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_deserialize_request() {
57        let json = r#"{"command": "show_status"}"#;
58        let req: Request = serde_json::from_str(json).unwrap();
59        assert_eq!(req.command, "show_status");
60    }
61
62    #[test]
63    fn test_serialize_ok_response() {
64        let resp = Response::ok(serde_json::json!({"state": "running"}));
65        let json = serde_json::to_string(&resp).unwrap();
66        assert!(json.contains("\"status\":\"ok\""));
67        assert!(json.contains("\"state\":\"running\""));
68        assert!(!json.contains("\"message\""));
69    }
70
71    #[test]
72    fn test_serialize_error_response() {
73        let resp = Response::error("unknown command: foo");
74        let json = serde_json::to_string(&resp).unwrap();
75        assert!(json.contains("\"status\":\"error\""));
76        assert!(json.contains("unknown command: foo"));
77        assert!(!json.contains("\"data\""));
78    }
79
80    #[test]
81    fn test_deserialize_unknown_fields_ignored() {
82        let json = r#"{"command": "show_peers", "extra": true}"#;
83        let req: Request = serde_json::from_str(json).unwrap();
84        assert_eq!(req.command, "show_peers");
85    }
86
87    #[test]
88    fn test_deserialize_request_with_params() {
89        let json = r#"{"command": "connect", "params": {"npub": "npub1abc", "address": "1.2.3.4:2121", "transport": "udp"}}"#;
90        let req: Request = serde_json::from_str(json).unwrap();
91        assert_eq!(req.command, "connect");
92        let params = req.params.unwrap();
93        assert_eq!(params["npub"], "npub1abc");
94        assert_eq!(params["transport"], "udp");
95    }
96
97    #[test]
98    fn test_deserialize_request_without_params() {
99        let json = r#"{"command": "show_status"}"#;
100        let req: Request = serde_json::from_str(json).unwrap();
101        assert_eq!(req.command, "show_status");
102        assert!(req.params.is_none());
103    }
104
105    #[test]
106    fn test_deserialize_malformed_request() {
107        let json = r#"{"not_command": "foo"}"#;
108        let result: Result<Request, _> = serde_json::from_str(json);
109        assert!(result.is_err());
110    }
111}