use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::{string::String, vec::Vec};
#[allow(dead_code)]
pub const PROTOCOL_VERSION: &str = "1.0";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HelloMessage {
pub version: String,
pub client: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub capabilities: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WelcomeMessage {
pub version: String,
pub server: String,
pub permissions: Vec<String>,
pub writable_records: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_subscriptions: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authenticated: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Request {
pub id: u64,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<JsonValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Response {
Success {
id: u64,
result: JsonValue,
},
Error {
id: u64,
error: ErrorObject,
},
}
impl Response {
pub fn success(id: u64, result: JsonValue) -> Self {
Self::Success { id, result }
}
pub fn error(id: u64, code: impl Into<String>, message: impl Into<String>) -> Self {
Self::Error {
id,
error: ErrorObject {
code: code.into(),
message: message.into(),
details: None,
},
}
}
pub fn error_with_details(
id: u64,
code: impl Into<String>,
message: impl Into<String>,
details: JsonValue,
) -> Self {
Self::Error {
id,
error: ErrorObject {
code: code.into(),
message: message.into(),
details: Some(details),
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorObject {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<JsonValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub subscription_id: String,
pub sequence: u64,
pub data: JsonValue,
pub timestamp: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub dropped: Option<u64>,
}
#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Message {
Hello { hello: HelloMessage },
Welcome { welcome: WelcomeMessage },
Request(Request),
Response(Response),
Event { event: Event },
}
#[allow(dead_code)] impl Message {
pub fn hello(client: impl Into<String>) -> Self {
Self::Hello {
hello: HelloMessage {
version: PROTOCOL_VERSION.to_string(),
client: client.into(),
capabilities: None,
auth_token: None,
},
}
}
pub fn welcome(server: impl Into<String>, permissions: Vec<String>) -> Self {
Self::Welcome {
welcome: WelcomeMessage {
version: PROTOCOL_VERSION.to_string(),
server: server.into(),
permissions,
writable_records: Vec::new(),
max_subscriptions: None,
authenticated: None,
},
}
}
pub fn request(id: u64, method: impl Into<String>, params: Option<JsonValue>) -> Self {
Self::Request(Request {
id,
method: method.into(),
params,
})
}
pub fn response_success(id: u64, result: JsonValue) -> Self {
Self::Response(Response::success(id, result))
}
pub fn response_error(id: u64, code: impl Into<String>, message: impl Into<String>) -> Self {
Self::Response(Response::error(id, code, message))
}
pub fn event(
subscription_id: impl Into<String>,
sequence: u64,
data: JsonValue,
timestamp: impl Into<String>,
) -> Self {
Self::Event {
event: Event {
subscription_id: subscription_id.into(),
sequence,
data,
timestamp: timestamp.into(),
dropped: None,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hello_serialization() {
let hello = HelloMessage {
version: "1.0".to_string(),
client: "test-client".to_string(),
capabilities: Some(vec!["read".to_string()]),
auth_token: None,
};
let json = serde_json::to_string(&hello).unwrap();
assert!(json.contains("\"version\":\"1.0\""));
assert!(json.contains("\"client\":\"test-client\""));
}
#[test]
fn test_request_serialization() {
let request = Request {
id: 1,
method: "record.list".to_string(),
params: Some(serde_json::json!({})),
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"id\":1"));
assert!(json.contains("\"method\":\"record.list\""));
}
#[test]
fn test_response_success() {
let response = Response::success(1, serde_json::json!({"status": "ok"}));
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"id\":1"));
assert!(json.contains("\"result\""));
assert!(json.contains("\"status\":\"ok\""));
}
#[test]
fn test_response_error() {
let response = Response::error(2, "NOT_FOUND", "Record not found");
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"id\":2"));
assert!(json.contains("\"error\""));
assert!(json.contains("\"code\":\"NOT_FOUND\""));
assert!(json.contains("\"message\":\"Record not found\""));
}
#[test]
fn test_event_serialization() {
let event = Event {
subscription_id: "sub-123".to_string(),
sequence: 42,
data: serde_json::json!({"temp": 23.5}),
timestamp: "1730379296.123456789".to_string(),
dropped: None,
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("\"subscription_id\":\"sub-123\""));
assert!(json.contains("\"sequence\":42"));
assert!(json.contains("\"temp\":23.5"));
}
#[test]
fn test_event_with_dropped() {
let event = Event {
subscription_id: "sub-456".to_string(),
sequence: 100,
data: serde_json::json!({"value": 1}),
timestamp: "1730379300.987654321".to_string(),
dropped: Some(5),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("\"dropped\":5"));
}
}