claude_codes/
protocol.rs

1//! JSON Lines protocol implementation for Claude communication.
2//!
3//! This module provides the [`Protocol`] struct with methods for:
4//! - Serializing messages to JSON Lines format
5//! - Deserializing JSON Lines into typed messages
6//!
7//! The JSON Lines format means each message is a complete JSON object on a single line,
8//! terminated by a newline character. This enables streaming communication where messages
9//! can be processed as they arrive.
10//!
11//! # Example
12//!
13//! ```
14//! use claude_codes::{Protocol, ClaudeInput};
15//!
16//! // Serialize a message
17//! let input = ClaudeInput::user_message("Hello!", uuid::Uuid::new_v4());
18//! let json_line = Protocol::serialize(&input)?;
19//! assert!(json_line.ends_with('\n'));
20//!
21//! // Deserialize a message
22//! let output = Protocol::deserialize::<serde_json::Value>(&json_line)?;
23//! # Ok::<(), Box<dyn std::error::Error>>(())
24//! ```
25
26use crate::error::{Error, Result};
27use crate::messages::{Event, Request, Response};
28use serde::{Deserialize, Serialize};
29
30/// Protocol handler for Claude Code JSON lines communication
31pub struct Protocol;
32
33impl Protocol {
34    /// Serialize a message to JSON lines format
35    pub fn serialize<T: Serialize>(message: &T) -> Result<String> {
36        let json = serde_json::to_string(message)?;
37        Ok(format!("{}\n", json))
38    }
39
40    /// Deserialize a JSON line into a message
41    pub fn deserialize<T: for<'de> Deserialize<'de>>(line: &str) -> Result<T> {
42        let trimmed = line.trim();
43        if trimmed.is_empty() {
44            return Err(Error::Protocol("Empty line".to_string()));
45        }
46        Ok(serde_json::from_str(trimmed)?)
47    }
48}
49
50/// Message envelope for routing different message types
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(tag = "message_class", rename_all = "snake_case")]
53pub enum MessageEnvelope {
54    Request(Request),
55    Response(Response),
56    Event(Event),
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::messages::*;
63
64    #[test]
65    fn test_serialize_deserialize() {
66        let request = Request {
67            message_type: "request".to_string(),
68            id: "test-123".to_string(),
69            session_id: Some("session-456".to_string()),
70            payload: RequestPayload::Initialize(InitializeRequest {
71                working_directory: Some("/home/user".to_string()),
72                environment: None,
73                capabilities: None,
74            }),
75            metadata: None,
76        };
77
78        let serialized = Protocol::serialize(&request).unwrap();
79        assert!(serialized.ends_with('\n'));
80
81        let deserialized: Request = Protocol::deserialize(&serialized).unwrap();
82        assert_eq!(deserialized.id, request.id);
83    }
84
85    #[test]
86    fn test_empty_line_error() {
87        let result: Result<Request> = Protocol::deserialize("");
88        assert!(result.is_err());
89    }
90
91    #[test]
92    fn test_invalid_json_error() {
93        let result: Result<Request> = Protocol::deserialize("not valid json");
94        assert!(result.is_err());
95    }
96}