soup_sdk/chat/parser/
raw.rs1use chrono::{DateTime, Utc};
2use std::sync::Arc;
3use thiserror::Error;
4
5use crate::chat::constants::{SEPARATOR_U8, message_codes::MessageCode};
6
7#[derive(Debug, Clone)]
8pub struct RawMessage {
9 pub code: MessageCode,
10 pub ret_code: u32,
11 pub body: Vec<String>,
12 pub received_time: DateTime<Utc>,
13 pub raw: Arc<[u8]>,
14}
15
16struct MessageHeader {
17 code: MessageCode,
18 ret_code: u32,
19}
20
21#[derive(Debug, Clone, Error)]
22pub enum ParseError {
23 #[error("프레임이 너무 짧습니다: {len} bytes")]
24 FrameTooShort { len: usize },
25
26 #[error("헤더 길이가 잘못되었습니다: {len} bytes")]
27 InvalidHeaderLength { len: usize },
28
29 #[error("필드가 없습니다: code={code}, index={index}, len={len}")]
30 MissingField {
31 code: MessageCode,
32 index: usize,
33 len: usize,
34 },
35
36 #[error("숫자 파싱 실패: code={code}, index={index}, value={value:?}, target={target}")]
37 InvalidNumber {
38 code: MessageCode,
39 index: usize,
40 value: String,
41 target: &'static str,
42 },
43
44 #[error("JSON 파싱 실패: code={code}, index={index}, reason={reason}")]
45 InvalidJson {
46 code: MessageCode,
47 index: usize,
48 reason: String,
49 },
50
51 #[error("프로토콜 파싱 실패: {0}")]
52 Protocol(String),
53}
54
55pub type ParseResult<T> = std::result::Result<T, ParseError>;
56
57impl RawMessage {
58 pub fn field(&self, index: usize) -> ParseResult<&str> {
59 self.body
60 .get(index)
61 .map(String::as_str)
62 .ok_or(ParseError::MissingField {
63 code: self.code,
64 index,
65 len: self.body.len(),
66 })
67 }
68
69 pub fn field_string(&self, index: usize) -> ParseResult<String> {
70 Ok(self.field(index)?.to_string())
71 }
72
73 pub fn parse_u32(&self, index: usize) -> ParseResult<u32> {
74 let value = self.field(index)?;
75 value.parse::<u32>().map_err(|_| ParseError::InvalidNumber {
76 code: self.code,
77 index,
78 value: value.to_string(),
79 target: "u32",
80 })
81 }
82
83 pub fn parse_usize_or_default(&self, index: usize, default: usize) -> usize {
84 self.field(index)
85 .ok()
86 .and_then(|value| value.parse::<usize>().ok())
87 .unwrap_or(default)
88 }
89
90 pub fn parse_u32_or_default(&self, index: usize, default: u32) -> u32 {
91 self.field(index)
92 .ok()
93 .and_then(|value| value.parse::<u32>().ok())
94 .unwrap_or(default)
95 }
96}
97
98pub fn parse_message(data: Arc<[u8]>) -> ParseResult<RawMessage> {
99 let now = Utc::now();
100
101 if data.len() < 14 {
102 return Err(ParseError::FrameTooShort { len: data.len() });
103 }
104
105 let header_bytes = &data[0..14];
106
107 let header = parse_header(header_bytes)?;
108
109 let body = &data[14..];
110
111 Ok(RawMessage {
112 code: header.code,
113 ret_code: header.ret_code,
114 body: parse_body(body),
115 received_time: now,
116 raw: data,
117 })
118}
119
120fn parse_header(header: &[u8]) -> ParseResult<MessageHeader> {
121 if header.len() != 14 {
122 return Err(ParseError::InvalidHeaderLength { len: header.len() });
123 }
124
125 Ok(MessageHeader {
126 code: parse_bytes_to_u32(&header[2..6]),
127 ret_code: parse_bytes_to_u32(&header[12..14]),
128 })
129}
130
131fn parse_body(body: &[u8]) -> Vec<String> {
132 if body.len() < 2 {
134 return Vec::new();
135 }
136
137 let data_to_process = &body[1..];
139
140 let separator_count = data_to_process
142 .iter()
143 .filter(|&&b| b == SEPARATOR_U8)
144 .count();
145 let mut result = Vec::with_capacity(separator_count + 1);
146
147 for byte_part in data_to_process.split(|&byte| byte == SEPARATOR_U8) {
149 match std::str::from_utf8(byte_part) {
151 Ok(s) => result.push(s.to_string()),
152 Err(_) => {
153 let cow_str = String::from_utf8_lossy(byte_part);
155 result.push(cow_str.into_owned());
156 }
157 }
158 }
159
160 result
161}
162
163fn parse_bytes_to_u32(bytes: &[u8]) -> u32 {
164 match std::str::from_utf8(bytes) {
166 Ok(s) => s.parse::<u32>().unwrap_or(0),
167 Err(_) => {
168 String::from_utf8_lossy(bytes).parse::<u32>().unwrap_or(0)
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use std::sync::Arc;
177
178 use super::*;
179
180 fn frame(code: u32, body: &[u8]) -> Arc<[u8]> {
181 let mut bytes = Vec::new();
182 bytes.extend_from_slice(&[27, 9]);
183 bytes.extend_from_slice(format!("{code:04}").as_bytes());
184 bytes.extend_from_slice(format!("{:06}", body.len()).as_bytes());
185 bytes.extend_from_slice(b"00");
186 bytes.extend_from_slice(body);
187 bytes.into()
188 }
189
190 #[test]
191 fn decodes_header_and_body_fields() {
192 let raw = parse_message(frame(5, b"\x0chello\x0cuser")).unwrap();
193
194 assert_eq!(raw.code, 5);
195 assert_eq!(raw.ret_code, 0);
196 assert_eq!(raw.body, vec!["hello".to_string(), "user".to_string()]);
197 }
198
199 #[test]
200 fn rejects_short_header() {
201 let short: Arc<[u8]> = vec![1_u8, 2, 3].into();
202 let err = parse_message(short).unwrap_err();
203
204 assert!(matches!(err, ParseError::FrameTooShort { len: 3 }));
205 }
206
207 #[test]
208 fn keeps_unknown_code_decodable() {
209 let raw = parse_message(frame(9999, b"\x0cnew-payload")).unwrap();
210
211 assert_eq!(raw.code, 9999);
212 assert_eq!(raw.body, vec!["new-payload".to_string()]);
213 }
214
215 #[test]
216 fn invalid_utf8_body_is_lossy_not_fatal() {
217 let raw = parse_message(frame(5, b"\x0c\xff")).unwrap();
218
219 assert_eq!(raw.body.len(), 1);
220 assert!(raw.body[0].contains('\u{fffd}'));
221 }
222}