doum_cli/llm/
parser.rs

1use crate::system::error::{DoumError, Result};
2use serde::{Deserialize, Serialize};
3
4/// 모드 선택 응답
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ModeSelectResponse {
7    pub mode: String,
8    pub reason: String,
9}
10
11/// Ask 모드 응답 (단순 문자열)
12pub type AskResponse = String;
13
14/// 명령 제안
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct CommandSuggestion {
17    pub cmd: String,
18    pub description: String,
19}
20
21/// Suggest 모드 응답
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct SuggestResponse {
24    pub suggestions: Vec<CommandSuggestion>,
25}
26
27/// Execute 모드 응답
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ExecuteResponse {
30    pub command: String,
31    pub description: String,
32    pub is_dangerous: bool,
33}
34
35/// 모드 선택 응답 파싱
36pub fn parse_mode_select(json_str: &str) -> Result<ModeSelectResponse> {
37    // JSON 추출 시도 (markdown 코드 블록 제거)
38    let cleaned = extract_json(json_str);
39    
40    serde_json::from_str(&cleaned)
41        .map_err(|e| DoumError::Parse(format!("모드 선택 응답 파싱 실패: {}", e)))
42}
43
44/// Suggest 응답 파싱
45pub fn parse_suggest(json_str: &str) -> Result<SuggestResponse> {
46    let cleaned = extract_json(json_str);
47    
48    serde_json::from_str(&cleaned)
49        .map_err(|e| DoumError::Parse(format!("Suggest 응답 파싱 실패: {}", e)))
50}
51
52/// Execute 응답 파싱
53pub fn parse_execute(json_str: &str) -> Result<ExecuteResponse> {
54    let cleaned = extract_json(json_str);
55    
56    serde_json::from_str(&cleaned)
57        .map_err(|e| DoumError::Parse(format!("Execute 응답 파싱 실패: {}", e)))
58}
59
60/// JSON 추출 (markdown 코드 블록이나 불필요한 텍스트 제거)
61fn extract_json(text: &str) -> String {
62    let text = text.trim();
63    
64    // ```json ... ``` 또는 ``` ... ``` 형식 처리
65    if let Some(start) = text.find("```")
66        && let Some(end) = text[start + 3..].find("```") {
67            let json_block = &text[start + 3..start + 3 + end];
68            // ```json 같은 언어 태그 제거
69            let json_content = if let Some(newline) = json_block.find('\n') {
70                &json_block[newline + 1..]
71            } else {
72                json_block
73            };
74            return json_content.trim().to_string();
75        }
76    
77    // { 로 시작하는 JSON 찾기
78    if let Some(start) = text.find('{')
79        && let Some(end) = text.rfind('}')
80            && end > start {
81                return text[start..=end].to_string();
82            }
83    
84    // 그대로 반환
85    text.to_string()
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_parse_mode_select() {
94        let json = r#"{"mode":"ask","reason":"사용자가 질문을 했습니다"}"#;
95        let result = parse_mode_select(json).unwrap();
96        assert_eq!(result.mode, "ask");
97        assert_eq!(result.reason, "사용자가 질문을 했습니다");
98    }
99
100    #[test]
101    fn test_parse_mode_select_with_markdown() {
102        let json = r#"
103```json
104{"mode":"execute","reason":"실행 요청"}
105```
106        "#;
107        let result = parse_mode_select(json).unwrap();
108        assert_eq!(result.mode, "execute");
109    }
110
111    #[test]
112    fn test_parse_suggest() {
113        let json = r#"
114{
115  "suggestions": [
116    {"cmd": "ls -la", "description": "모든 파일 나열"},
117    {"cmd": "dir", "description": "디렉터리 내용 보기"}
118  ]
119}
120        "#;
121        let result = parse_suggest(json).unwrap();
122        assert_eq!(result.suggestions.len(), 2);
123        assert_eq!(result.suggestions[0].cmd, "ls -la");
124    }
125
126    #[test]
127    fn test_parse_execute() {
128        let json = r#"
129{
130  "command": "echo hello",
131  "description": "hello 출력",
132  "is_dangerous": false
133}
134        "#;
135        let result = parse_execute(json).unwrap();
136        assert_eq!(result.command, "echo hello");
137        assert!(!result.is_dangerous);
138    }
139
140    #[test]
141    fn test_parse_execute_dangerous() {
142        let json = r#"{"command":"rm -rf /","description":"위험","is_dangerous":true}"#;
143        let result = parse_execute(json).unwrap();
144        assert!(result.is_dangerous);
145    }
146
147    #[test]
148    fn test_extract_json() {
149        let text = "Some text before\n{\"key\":\"value\"}\nSome text after";
150        let result = extract_json(text);
151        assert_eq!(result, r#"{"key":"value"}"#);
152    }
153
154    #[test]
155    fn test_extract_json_with_code_block() {
156        let text = "```json\n{\"key\":\"value\"}\n```";
157        let result = extract_json(text);
158        assert_eq!(result, r#"{"key":"value"}"#);
159    }
160}