Skip to main content

aimdb_client/
protocol.rs

1//! AimX Protocol Types and Utilities
2//!
3//! This module re-exports protocol types from `aimdb-core` and adds CLI-specific
4//! utilities for NDJSON serialization and convenience methods.
5
6use serde::{Deserialize, Serialize};
7
8// Re-export protocol types from aimdb-core
9pub use aimdb_core::remote::{
10    ErrorObject, Event, HelloMessage, RecordMetadata, Request, Response, WelcomeMessage,
11};
12
13/// Protocol version supported by this client
14pub const PROTOCOL_VERSION: &str = "1.0";
15
16/// Client identifier
17pub const CLIENT_NAME: &str = "aimdb-cli";
18
19/// CLI-specific convenience methods for Request
20pub trait RequestExt {
21    /// Create a new request
22    #[allow(clippy::new_ret_no_self)]
23    fn new(id: u64, method: impl Into<String>) -> Request;
24
25    /// Create a request with parameters
26    fn with_params(id: u64, method: impl Into<String>, params: serde_json::Value) -> Request;
27}
28
29impl RequestExt for Request {
30    fn new(id: u64, method: impl Into<String>) -> Request {
31        Request {
32            id,
33            method: method.into(),
34            params: None,
35        }
36    }
37
38    fn with_params(id: u64, method: impl Into<String>, params: serde_json::Value) -> Request {
39        Request {
40            id,
41            method: method.into(),
42            params: Some(params),
43        }
44    }
45}
46
47/// CLI-specific convenience methods for Response
48pub trait ResponseExt {
49    /// Extract result or return error
50    fn into_result(self) -> Result<serde_json::Value, ErrorObject>;
51}
52
53impl ResponseExt for Response {
54    fn into_result(self) -> Result<serde_json::Value, ErrorObject> {
55        match self {
56            Response::Success { result, .. } => Ok(result),
57            Response::Error { error, .. } => Err(error),
58        }
59    }
60}
61
62/// Create default CLI hello message
63pub fn cli_hello() -> HelloMessage {
64    HelloMessage {
65        version: PROTOCOL_VERSION.to_string(),
66        client: CLIENT_NAME.to_string(),
67        capabilities: None,
68        auth_token: None,
69    }
70}
71
72/// Event message wrapper from subscription (for NDJSON parsing)
73#[derive(Debug, Clone, Deserialize)]
74pub struct EventMessage {
75    pub event: Event,
76}
77
78/// Helper to serialize a message as NDJSON line
79pub fn serialize_message<T: Serialize>(msg: &T) -> Result<String, serde_json::Error> {
80    Ok(format!("{}\n", serde_json::to_string(msg)?))
81}
82
83/// Helper to parse NDJSON line into message
84pub fn parse_message<T: for<'de> Deserialize<'de>>(line: &str) -> Result<T, serde_json::Error> {
85    serde_json::from_str(line.trim())
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_request_serialization() {
94        let req = Request::new(1, "record.list");
95        let json = serialize_message(&req).unwrap();
96        assert!(json.contains("\"method\":\"record.list\""));
97        assert!(json.ends_with('\n'));
98    }
99
100    #[test]
101    fn test_response_parsing() {
102        let json = r#"{"id":1,"result":{"status":"ok"}}"#;
103        let resp: Response = parse_message(json).unwrap();
104        let result = resp.into_result().unwrap();
105        assert_eq!(result["status"], "ok");
106    }
107
108    #[test]
109    fn test_cli_hello() {
110        let hello = cli_hello();
111        assert_eq!(hello.version, PROTOCOL_VERSION);
112        assert_eq!(hello.client, CLIENT_NAME);
113        assert!(hello.auth_token.is_none());
114    }
115}