Skip to main content

spn_client/
protocol.rs

1//! Protocol types for daemon communication.
2//!
3//! The protocol uses length-prefixed JSON over Unix sockets.
4//!
5//! ## Wire Format
6//!
7//! ```text
8//! [4 bytes: message length (big-endian u32)][JSON payload]
9//! ```
10//!
11//! ## Example
12//!
13//! Request:
14//! ```json
15//! { "cmd": "GET_SECRET", "provider": "anthropic" }
16//! ```
17//!
18//! Response:
19//! ```json
20//! { "ok": true, "secret": "sk-ant-..." }
21//! ```
22
23use serde::{Deserialize, Serialize};
24
25/// Request sent to the daemon.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(tag = "cmd")]
28pub enum Request {
29    /// Ping the daemon to check it's alive.
30    #[serde(rename = "PING")]
31    Ping,
32
33    /// Get a secret for a provider.
34    #[serde(rename = "GET_SECRET")]
35    GetSecret { provider: String },
36
37    /// Check if a secret exists.
38    #[serde(rename = "HAS_SECRET")]
39    HasSecret { provider: String },
40
41    /// List all available providers.
42    #[serde(rename = "LIST_PROVIDERS")]
43    ListProviders,
44}
45
46/// Response from the daemon.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(untagged)]
49pub enum Response {
50    /// Successful ping response.
51    Pong { version: String },
52
53    /// Secret value response.
54    Secret { value: String },
55
56    /// Secret existence check response.
57    Exists { exists: bool },
58
59    /// Provider list response.
60    Providers { providers: Vec<String> },
61
62    /// Error response.
63    Error { message: String },
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_request_serialization() {
72        let ping = Request::Ping;
73        let json = serde_json::to_string(&ping).unwrap();
74        assert_eq!(json, r#"{"cmd":"PING"}"#);
75
76        let get_secret = Request::GetSecret {
77            provider: "anthropic".to_string(),
78        };
79        let json = serde_json::to_string(&get_secret).unwrap();
80        assert_eq!(json, r#"{"cmd":"GET_SECRET","provider":"anthropic"}"#);
81
82        let has_secret = Request::HasSecret {
83            provider: "openai".to_string(),
84        };
85        let json = serde_json::to_string(&has_secret).unwrap();
86        assert_eq!(json, r#"{"cmd":"HAS_SECRET","provider":"openai"}"#);
87
88        let list = Request::ListProviders;
89        let json = serde_json::to_string(&list).unwrap();
90        assert_eq!(json, r#"{"cmd":"LIST_PROVIDERS"}"#);
91    }
92
93    #[test]
94    fn test_response_deserialization() {
95        // Pong
96        let json = r#"{"version":"0.9.0"}"#;
97        let response: Response = serde_json::from_str(json).unwrap();
98        assert!(matches!(response, Response::Pong { version } if version == "0.9.0"));
99
100        // Secret
101        let json = r#"{"value":"sk-test-123"}"#;
102        let response: Response = serde_json::from_str(json).unwrap();
103        assert!(matches!(response, Response::Secret { value } if value == "sk-test-123"));
104
105        // Exists
106        let json = r#"{"exists":true}"#;
107        let response: Response = serde_json::from_str(json).unwrap();
108        assert!(matches!(response, Response::Exists { exists } if exists));
109
110        // Providers
111        let json = r#"{"providers":["anthropic","openai"]}"#;
112        let response: Response = serde_json::from_str(json).unwrap();
113        assert!(
114            matches!(response, Response::Providers { providers } if providers == vec!["anthropic", "openai"])
115        );
116
117        // Error
118        let json = r#"{"message":"Not found"}"#;
119        let response: Response = serde_json::from_str(json).unwrap();
120        assert!(matches!(response, Response::Error { message } if message == "Not found"));
121    }
122}