use chrono::{DateTime, Utc};
use std::sync::Arc;
use thiserror::Error;
use crate::chat::constants::{SEPARATOR_U8, message_codes::MessageCode};
#[derive(Debug, Clone)]
pub struct RawMessage {
pub code: MessageCode,
pub ret_code: u32,
pub body: Vec<String>,
pub received_time: DateTime<Utc>,
pub raw: Arc<[u8]>,
}
struct MessageHeader {
code: MessageCode,
ret_code: u32,
}
#[derive(Debug, Clone, Error)]
pub enum ParseError {
#[error("프레임이 너무 짧습니다: {len} bytes")]
FrameTooShort { len: usize },
#[error("헤더 길이가 잘못되었습니다: {len} bytes")]
InvalidHeaderLength { len: usize },
#[error("필드가 없습니다: code={code}, index={index}, len={len}")]
MissingField {
code: MessageCode,
index: usize,
len: usize,
},
#[error("숫자 파싱 실패: code={code}, index={index}, value={value:?}, target={target}")]
InvalidNumber {
code: MessageCode,
index: usize,
value: String,
target: &'static str,
},
#[error("JSON 파싱 실패: code={code}, index={index}, reason={reason}")]
InvalidJson {
code: MessageCode,
index: usize,
reason: String,
},
#[error("프로토콜 파싱 실패: {0}")]
Protocol(String),
}
pub type ParseResult<T> = std::result::Result<T, ParseError>;
impl RawMessage {
pub fn field(&self, index: usize) -> ParseResult<&str> {
self.body
.get(index)
.map(String::as_str)
.ok_or(ParseError::MissingField {
code: self.code,
index,
len: self.body.len(),
})
}
pub fn field_string(&self, index: usize) -> ParseResult<String> {
Ok(self.field(index)?.to_string())
}
pub fn parse_u32(&self, index: usize) -> ParseResult<u32> {
let value = self.field(index)?;
value.parse::<u32>().map_err(|_| ParseError::InvalidNumber {
code: self.code,
index,
value: value.to_string(),
target: "u32",
})
}
pub fn parse_usize_or_default(&self, index: usize, default: usize) -> usize {
self.field(index)
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(default)
}
pub fn parse_u32_or_default(&self, index: usize, default: u32) -> u32 {
self.field(index)
.ok()
.and_then(|value| value.parse::<u32>().ok())
.unwrap_or(default)
}
}
pub fn parse_message(data: Arc<[u8]>) -> ParseResult<RawMessage> {
let now = Utc::now();
if data.len() < 14 {
return Err(ParseError::FrameTooShort { len: data.len() });
}
let header_bytes = &data[0..14];
let header = parse_header(header_bytes)?;
let body = &data[14..];
Ok(RawMessage {
code: header.code,
ret_code: header.ret_code,
body: parse_body(body),
received_time: now,
raw: data,
})
}
fn parse_header(header: &[u8]) -> ParseResult<MessageHeader> {
if header.len() != 14 {
return Err(ParseError::InvalidHeaderLength { len: header.len() });
}
Ok(MessageHeader {
code: parse_bytes_to_u32(&header[2..6]),
ret_code: parse_bytes_to_u32(&header[12..14]),
})
}
fn parse_body(body: &[u8]) -> Vec<String> {
if body.len() < 2 {
return Vec::new();
}
let data_to_process = &body[1..];
let separator_count = data_to_process
.iter()
.filter(|&&b| b == SEPARATOR_U8)
.count();
let mut result = Vec::with_capacity(separator_count + 1);
for byte_part in data_to_process.split(|&byte| byte == SEPARATOR_U8) {
match std::str::from_utf8(byte_part) {
Ok(s) => result.push(s.to_string()),
Err(_) => {
let cow_str = String::from_utf8_lossy(byte_part);
result.push(cow_str.into_owned());
}
}
}
result
}
fn parse_bytes_to_u32(bytes: &[u8]) -> u32 {
match std::str::from_utf8(bytes) {
Ok(s) => s.parse::<u32>().unwrap_or(0),
Err(_) => {
String::from_utf8_lossy(bytes).parse::<u32>().unwrap_or(0)
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
fn frame(code: u32, body: &[u8]) -> Arc<[u8]> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&[27, 9]);
bytes.extend_from_slice(format!("{code:04}").as_bytes());
bytes.extend_from_slice(format!("{:06}", body.len()).as_bytes());
bytes.extend_from_slice(b"00");
bytes.extend_from_slice(body);
bytes.into()
}
#[test]
fn decodes_header_and_body_fields() {
let raw = parse_message(frame(5, b"\x0chello\x0cuser")).unwrap();
assert_eq!(raw.code, 5);
assert_eq!(raw.ret_code, 0);
assert_eq!(raw.body, vec!["hello".to_string(), "user".to_string()]);
}
#[test]
fn rejects_short_header() {
let short: Arc<[u8]> = vec![1_u8, 2, 3].into();
let err = parse_message(short).unwrap_err();
assert!(matches!(err, ParseError::FrameTooShort { len: 3 }));
}
#[test]
fn keeps_unknown_code_decodable() {
let raw = parse_message(frame(9999, b"\x0cnew-payload")).unwrap();
assert_eq!(raw.code, 9999);
assert_eq!(raw.body, vec!["new-payload".to_string()]);
}
#[test]
fn invalid_utf8_body_is_lossy_not_fatal() {
let raw = parse_message(frame(5, b"\x0c\xff")).unwrap();
assert_eq!(raw.body.len(), 1);
assert!(raw.body[0].contains('\u{fffd}'));
}
}