use crate::errors::{ClaudeError, MessageParseError, Result};
use crate::types::messages::Message;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ParsingMode {
#[default]
Traditional,
ZeroCopy,
}
pub struct MessageParser;
impl MessageParser {
pub fn parse(data: serde_json::Value) -> Result<Message> {
serde_json::from_value(data).map_err(|e| {
MessageParseError::new(format!("Failed to parse message: {}", e), None).into()
})
}
}
pub struct ZeroCopyMessageParser;
impl ZeroCopyMessageParser {
pub fn parse(json: &str) -> Result<Message> {
serde_json::from_str(json).map_err(|e| {
ClaudeError::MessageParse(MessageParseError::new(
format!("Failed to parse message: {}", e),
Some(serde_json::Value::String(json.to_string())),
))
})
}
#[allow(dead_code)]
pub fn parse_bytes(bytes: &[u8]) -> Result<Message> {
let json = std::str::from_utf8(bytes).map_err(|e| {
ClaudeError::MessageParse(MessageParseError::new(
format!("Invalid UTF-8 in message: {}", e),
None,
))
})?;
Self::parse(json)
}
}
pub fn parse_with_mode(json: &str, mode: ParsingMode) -> Result<Message> {
match mode {
ParsingMode::Traditional => {
serde_json::from_str(json).map_err(|e| {
ClaudeError::MessageParse(MessageParseError::new(
format!("Failed to parse JSON: {}", e),
None,
))
})
}
ParsingMode::ZeroCopy => ZeroCopyMessageParser::parse(json),
}
}
#[allow(dead_code)]
pub fn parse_from_value(value: serde_json::Value) -> Result<Message> {
MessageParser::parse(value)
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageKind {
Assistant,
System,
Result,
StreamEvent,
User,
Control,
Unknown,
}
#[allow(dead_code)]
impl MessageKind {
pub fn detect(json: &str) -> Self {
let trimmed = json.trim();
if trimmed.contains(r#""type":"assistant""#) || trimmed.contains(r#""type": "assistant""#) {
return MessageKind::Assistant;
}
if trimmed.contains(r#""type":"system""#) || trimmed.contains(r#""type": "system""#) {
return MessageKind::System;
}
if trimmed.contains(r#""type":"result""#) || trimmed.contains(r#""type": "result""#) {
return MessageKind::Result;
}
if trimmed.contains(r#""type":"stream_event""#) || trimmed.contains(r#""type": "stream_event""#)
{
return MessageKind::StreamEvent;
}
if trimmed.contains(r#""type":"user""#) || trimmed.contains(r#""type": "user""#) {
return MessageKind::User;
}
if trimmed.contains(r#""type":"control""#) || trimmed.contains(r#""type": "control""#) {
return MessageKind::Control;
}
MessageKind::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_parser() {
let json = serde_json::json!({
"type": "assistant",
"message": {
"role": "assistant",
"content": [{"type": "text", "text": "Hello, world!"}]
}
});
let result = MessageParser::parse(json);
assert!(result.is_ok());
}
#[test]
fn test_zero_copy_parser_assistant() {
let json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
let result = ZeroCopyMessageParser::parse(json);
assert!(result.is_ok());
let message = result.unwrap();
match message {
Message::Assistant(msg) => {
assert!(!msg.message.content.is_empty());
}
_ => panic!("Expected Assistant message"),
}
}
#[test]
fn test_zero_copy_parser_system() {
let json = r#"{"type":"system","subtype":"init","cwd":"/home/user","session_id":"test-123"}"#;
let result = ZeroCopyMessageParser::parse(json);
assert!(result.is_ok());
let message = result.unwrap();
match message {
Message::System(msg) => {
assert_eq!(msg.subtype, "init");
}
_ => panic!("Expected System message"),
}
}
#[test]
fn test_zero_copy_parser_result() {
let json = r#"{"type":"result","subtype":"complete","result":"Task completed","session_id":"test-123","cost_usd":0.001,"duration_ms":500,"duration_api_ms":300,"num_turns":1,"total_cost_usd":0.001,"is_error":false}"#;
let result = ZeroCopyMessageParser::parse(json);
if result.is_err() {
eprintln!("Error: {:?}", result.as_ref().err());
}
assert!(result.is_ok());
let message = result.unwrap();
match message {
Message::Result(msg) => {
assert_eq!(msg.result, Some("Task completed".to_string()));
assert!(!msg.is_error);
}
_ => panic!("Expected Result message"),
}
}
#[test]
fn test_zero_copy_parser_invalid_json() {
let json = r#"{"type":"assistant","invalid"#;
let result = ZeroCopyMessageParser::parse(json);
assert!(result.is_err());
}
#[test]
fn test_zero_copy_parser_bytes() {
let json = br#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
let result = ZeroCopyMessageParser::parse_bytes(json);
assert!(result.is_ok());
}
#[test]
fn test_zero_copy_parser_bytes_invalid_utf8() {
let invalid_bytes: &[u8] = &[0xff, 0xfe, 0xfd];
let result = ZeroCopyMessageParser::parse_bytes(invalid_bytes);
assert!(result.is_err());
}
#[test]
fn test_message_kind_detect_assistant() {
let json = r#"{"type":"assistant","message":{}}"#;
assert_eq!(MessageKind::detect(json), MessageKind::Assistant);
}
#[test]
fn test_message_kind_detect_system() {
let json = r#"{"type":"system","subtype":"init"}"#;
assert_eq!(MessageKind::detect(json), MessageKind::System);
}
#[test]
fn test_message_kind_detect_result() {
let json = r#"{"type":"result","session_id":"123"}"#;
assert_eq!(MessageKind::detect(json), MessageKind::Result);
}
#[test]
fn test_message_kind_detect_stream_event() {
let json = r#"{"type":"stream_event","event":"text"}}"#;
assert_eq!(MessageKind::detect(json), MessageKind::StreamEvent);
}
#[test]
fn test_message_kind_detect_user() {
let json = r#"{"type":"user","text":"Hello"}"#;
assert_eq!(MessageKind::detect(json), MessageKind::User);
}
#[test]
fn test_message_kind_detect_unknown() {
let json = r#"{"foo":"bar"}"#;
assert_eq!(MessageKind::detect(json), MessageKind::Unknown);
}
#[test]
fn test_message_kind_detect_with_spaces() {
let json = r#"{"type": "assistant", "message": {}}"#;
assert_eq!(MessageKind::detect(json), MessageKind::Assistant);
}
#[test]
fn test_parse_with_mode_zero_copy() {
let json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
let result = parse_with_mode(json, ParsingMode::ZeroCopy);
assert!(result.is_ok());
let message = result.unwrap();
match message {
Message::Assistant(msg) => {
assert!(!msg.message.content.is_empty());
}
_ => panic!("Expected Assistant message"),
}
}
#[test]
fn test_parse_with_mode_traditional() {
let json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
let result = parse_with_mode(json, ParsingMode::Traditional);
assert!(result.is_ok());
let message = result.unwrap();
match message {
Message::Assistant(msg) => {
assert!(!msg.message.content.is_empty());
}
_ => panic!("Expected Assistant message"),
}
}
#[test]
fn test_parsing_mode_default() {
assert_eq!(ParsingMode::default(), ParsingMode::Traditional);
}
}