aimdb_core/remote/
protocol.rs

1//! AimX v1 Protocol Message Types
2//!
3//! Defines request, response, and event types for the remote access protocol.
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value as JsonValue;
7use std::{string::String, vec::Vec};
8
9// Allow dead code for now - these are part of the public API for future implementation
10#[allow(dead_code)]
11pub const PROTOCOL_VERSION: &str = "1.0";
12
13/// Client hello message
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct HelloMessage {
16    /// Protocol version
17    pub version: String,
18
19    /// Client identification string
20    pub client: String,
21
22    /// Desired capabilities (optional)
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub capabilities: Option<Vec<String>>,
25
26    /// Authentication token (optional)
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub auth_token: Option<String>,
29}
30
31/// Server welcome message
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct WelcomeMessage {
34    /// Protocol version
35    pub version: String,
36
37    /// Server identification string
38    pub server: String,
39
40    /// Granted permissions
41    pub permissions: Vec<String>,
42
43    /// Records that allow writes (empty for read-only)
44    pub writable_records: Vec<String>,
45
46    /// Maximum subscriptions per connection (optional)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub max_subscriptions: Option<usize>,
49
50    /// Whether client is authenticated
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub authenticated: Option<bool>,
53}
54
55/// Request message from client
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct Request {
58    /// Unique request identifier
59    pub id: u64,
60
61    /// Method name
62    pub method: String,
63
64    /// Method parameters (optional)
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub params: Option<JsonValue>,
67}
68
69/// Response message from server
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(untagged)]
72pub enum Response {
73    /// Success response
74    Success {
75        /// Request ID
76        id: u64,
77        /// Result value
78        result: JsonValue,
79    },
80    /// Error response
81    Error {
82        /// Request ID
83        id: u64,
84        /// Error details
85        error: ErrorObject,
86    },
87}
88
89impl Response {
90    /// Creates a success response
91    pub fn success(id: u64, result: JsonValue) -> Self {
92        Self::Success { id, result }
93    }
94
95    /// Creates an error response
96    pub fn error(id: u64, code: impl Into<String>, message: impl Into<String>) -> Self {
97        Self::Error {
98            id,
99            error: ErrorObject {
100                code: code.into(),
101                message: message.into(),
102                details: None,
103            },
104        }
105    }
106
107    /// Creates an error response with details
108    pub fn error_with_details(
109        id: u64,
110        code: impl Into<String>,
111        message: impl Into<String>,
112        details: JsonValue,
113    ) -> Self {
114        Self::Error {
115            id,
116            error: ErrorObject {
117                code: code.into(),
118                message: message.into(),
119                details: Some(details),
120            },
121        }
122    }
123}
124
125/// Error object in response
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ErrorObject {
128    /// Error code
129    pub code: String,
130
131    /// Human-readable error message
132    pub message: String,
133
134    /// Additional error details (optional)
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub details: Option<JsonValue>,
137}
138
139/// Event message from server (subscription push)
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct Event {
142    /// Subscription identifier
143    pub subscription_id: String,
144
145    /// Monotonic sequence number
146    pub sequence: u64,
147
148    /// Event data (record value)
149    pub data: JsonValue,
150
151    /// Unix timestamp in "secs.nanosecs" format (e.g., "1730379296.123456789")
152    pub timestamp: String,
153
154    /// Number of dropped events since last delivery (optional)
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub dropped: Option<u64>,
157}
158
159/// Top-level message envelope for protocol communication
160#[allow(dead_code)] // Part of public API for future use
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(untagged)]
163pub enum Message {
164    /// Client hello
165    Hello { hello: HelloMessage },
166    /// Server welcome
167    Welcome { welcome: WelcomeMessage },
168    /// Client request
169    Request(Request),
170    /// Server response
171    Response(Response),
172    /// Server event
173    Event { event: Event },
174}
175
176#[allow(dead_code)] // Helper methods for future implementation
177impl Message {
178    /// Creates a hello message
179    pub fn hello(client: impl Into<String>) -> Self {
180        Self::Hello {
181            hello: HelloMessage {
182                version: PROTOCOL_VERSION.to_string(),
183                client: client.into(),
184                capabilities: None,
185                auth_token: None,
186            },
187        }
188    }
189
190    /// Creates a welcome message
191    pub fn welcome(server: impl Into<String>, permissions: Vec<String>) -> Self {
192        Self::Welcome {
193            welcome: WelcomeMessage {
194                version: PROTOCOL_VERSION.to_string(),
195                server: server.into(),
196                permissions,
197                writable_records: Vec::new(),
198                max_subscriptions: None,
199                authenticated: None,
200            },
201        }
202    }
203
204    /// Creates a request message
205    pub fn request(id: u64, method: impl Into<String>, params: Option<JsonValue>) -> Self {
206        Self::Request(Request {
207            id,
208            method: method.into(),
209            params,
210        })
211    }
212
213    /// Creates a success response message
214    pub fn response_success(id: u64, result: JsonValue) -> Self {
215        Self::Response(Response::success(id, result))
216    }
217
218    /// Creates an error response message
219    pub fn response_error(id: u64, code: impl Into<String>, message: impl Into<String>) -> Self {
220        Self::Response(Response::error(id, code, message))
221    }
222
223    /// Creates an event message
224    pub fn event(
225        subscription_id: impl Into<String>,
226        sequence: u64,
227        data: JsonValue,
228        timestamp: impl Into<String>,
229    ) -> Self {
230        Self::Event {
231            event: Event {
232                subscription_id: subscription_id.into(),
233                sequence,
234                data,
235                timestamp: timestamp.into(),
236                dropped: None,
237            },
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_hello_serialization() {
248        let hello = HelloMessage {
249            version: "1.0".to_string(),
250            client: "test-client".to_string(),
251            capabilities: Some(vec!["read".to_string()]),
252            auth_token: None,
253        };
254
255        let json = serde_json::to_string(&hello).unwrap();
256        assert!(json.contains("\"version\":\"1.0\""));
257        assert!(json.contains("\"client\":\"test-client\""));
258    }
259
260    #[test]
261    fn test_request_serialization() {
262        let request = Request {
263            id: 1,
264            method: "record.list".to_string(),
265            params: Some(serde_json::json!({})),
266        };
267
268        let json = serde_json::to_string(&request).unwrap();
269        assert!(json.contains("\"id\":1"));
270        assert!(json.contains("\"method\":\"record.list\""));
271    }
272
273    #[test]
274    fn test_response_success() {
275        let response = Response::success(1, serde_json::json!({"status": "ok"}));
276
277        let json = serde_json::to_string(&response).unwrap();
278        assert!(json.contains("\"id\":1"));
279        assert!(json.contains("\"result\""));
280        assert!(json.contains("\"status\":\"ok\""));
281    }
282
283    #[test]
284    fn test_response_error() {
285        let response = Response::error(2, "NOT_FOUND", "Record not found");
286
287        let json = serde_json::to_string(&response).unwrap();
288        assert!(json.contains("\"id\":2"));
289        assert!(json.contains("\"error\""));
290        assert!(json.contains("\"code\":\"NOT_FOUND\""));
291        assert!(json.contains("\"message\":\"Record not found\""));
292    }
293
294    #[test]
295    fn test_event_serialization() {
296        let event = Event {
297            subscription_id: "sub-123".to_string(),
298            sequence: 42,
299            data: serde_json::json!({"temp": 23.5}),
300            timestamp: "1730379296.123456789".to_string(),
301            dropped: None,
302        };
303
304        let json = serde_json::to_string(&event).unwrap();
305        assert!(json.contains("\"subscription_id\":\"sub-123\""));
306        assert!(json.contains("\"sequence\":42"));
307        assert!(json.contains("\"temp\":23.5"));
308    }
309
310    #[test]
311    fn test_event_with_dropped() {
312        let event = Event {
313            subscription_id: "sub-456".to_string(),
314            sequence: 100,
315            data: serde_json::json!({"value": 1}),
316            timestamp: "1730379300.987654321".to_string(),
317            dropped: Some(5),
318        };
319
320        let json = serde_json::to_string(&event).unwrap();
321        assert!(json.contains("\"dropped\":5"));
322    }
323}