Skip to main content

claude_code_rs/
message_parser.rs

1use serde_json::Value;
2
3use crate::error::{Error, Result};
4use crate::types::messages::{AssistantMessage, Message, ResultMessage, UserMessage};
5
6/// Parse a raw JSON value from the CLI stream into a typed Message.
7///
8/// The CLI emits newline-delimited JSON with a top-level `type` field.
9/// Each type has a different structure:
10/// - `"assistant"` and `"user"`: have a nested `"message"` object
11/// - `"result"`: top-level fields (no message wrapper)
12/// - `"system"`: has a `"subtype"` field
13/// - Others: preserved as Unknown
14pub fn parse_message(raw: Value) -> Result<Message> {
15    let msg_type = raw
16        .get("type")
17        .and_then(|v| v.as_str())
18        .ok_or_else(|| Error::MessageParse {
19            reason: "missing 'type' field".into(),
20        })?;
21
22    match msg_type {
23        "assistant" => parse_assistant(raw),
24        "user" => parse_user(raw),
25        "result" => parse_result(raw),
26        "system" => parse_system(raw),
27        other => Ok(Message::Unknown {
28            message_type: other.to_string(),
29            raw,
30        }),
31    }
32}
33
34fn parse_assistant(raw: Value) -> Result<Message> {
35    // The assistant message body is in the "message" field.
36    let message_value = raw.get("message").cloned().unwrap_or(raw.clone());
37
38    let message: AssistantMessage =
39        serde_json::from_value(message_value).map_err(|e| Error::MessageParse {
40            reason: format!("assistant message parse failed: {e}"),
41        })?;
42
43    Ok(Message::Assistant { message })
44}
45
46fn parse_user(raw: Value) -> Result<Message> {
47    let message_value = raw.get("message").cloned().unwrap_or(raw.clone());
48
49    let message: UserMessage =
50        serde_json::from_value(message_value).map_err(|e| Error::MessageParse {
51            reason: format!("user message parse failed: {e}"),
52        })?;
53
54    Ok(Message::User { message })
55}
56
57fn parse_result(raw: Value) -> Result<Message> {
58    // Result messages have their fields at the top level (no "message" wrapper).
59    let result: ResultMessage =
60        serde_json::from_value(raw).map_err(|e| Error::MessageParse {
61            reason: format!("result message parse failed: {e}"),
62        })?;
63
64    Ok(Message::Result { result })
65}
66
67fn parse_system(raw: Value) -> Result<Message> {
68    let subtype = raw
69        .get("subtype")
70        .and_then(|v| v.as_str())
71        .unwrap_or("unknown")
72        .to_string();
73
74    Ok(Message::System {
75        subtype,
76        data: raw,
77    })
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn parse_assistant_message() {
86        let raw = serde_json::json!({
87            "type": "assistant",
88            "message": {
89                "model": "claude-sonnet-4-5",
90                "content": [
91                    {"type": "text", "text": "Hello!"}
92                ]
93            }
94        });
95        let msg = parse_message(raw).unwrap();
96        assert!(matches!(msg, Message::Assistant { .. }));
97        assert_eq!(msg.text().unwrap(), "Hello!");
98    }
99
100    #[test]
101    fn parse_user_message() {
102        let raw = serde_json::json!({
103            "type": "user",
104            "message": {
105                "content": "What is 2+2?"
106            }
107        });
108        let msg = parse_message(raw).unwrap();
109        assert!(matches!(msg, Message::User { .. }));
110    }
111
112    #[test]
113    fn parse_result_message() {
114        let raw = serde_json::json!({
115            "type": "result",
116            "subtype": "success",
117            "is_error": false,
118            "duration_ms": 1234.0,
119            "num_turns": 3,
120            "session_id": "sess_123",
121            "total_cost_usd": 0.05
122        });
123        let msg = parse_message(raw).unwrap();
124        assert!(msg.is_result());
125        assert!(!msg.is_error());
126        assert_eq!(msg.session_id(), Some("sess_123"));
127    }
128
129    #[test]
130    fn parse_system_message() {
131        let raw = serde_json::json!({
132            "type": "system",
133            "subtype": "init",
134            "data": {"version": "2.1.0"}
135        });
136        let msg = parse_message(raw).unwrap();
137        match msg {
138            Message::System { subtype, .. } => assert_eq!(subtype, "init"),
139            _ => panic!("expected System"),
140        }
141    }
142
143    #[test]
144    fn parse_unknown_type() {
145        let raw = serde_json::json!({
146            "type": "stream_event",
147            "event": {"delta": "hello"}
148        });
149        let msg = parse_message(raw).unwrap();
150        match msg {
151            Message::Unknown { message_type, .. } => assert_eq!(message_type, "stream_event"),
152            _ => panic!("expected Unknown"),
153        }
154    }
155
156    #[test]
157    fn parse_missing_type() {
158        let raw = serde_json::json!({"data": "oops"});
159        assert!(parse_message(raw).is_err());
160    }
161
162    #[test]
163    fn parse_assistant_with_tool_use() {
164        let raw = serde_json::json!({
165            "type": "assistant",
166            "message": {
167                "model": "claude-sonnet-4-5",
168                "content": [
169                    {"type": "text", "text": "Let me run that."},
170                    {"type": "tool_use", "id": "tu_1", "name": "Bash", "input": {"command": "ls"}}
171                ]
172            }
173        });
174        let msg = parse_message(raw).unwrap();
175        if let Message::Assistant { message } = msg {
176            assert_eq!(message.content.len(), 2);
177        } else {
178            panic!("expected Assistant");
179        }
180    }
181}