oxicode_ai/utils/
json_parse.rs1use crate::messages::AssistantMessage;
9
10const VALID_JSON_ESCAPES: &[char] = &['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u'];
12
13fn is_control_character(ch: char) -> bool {
15 ch as u32 <= 0x1F
16}
17
18fn escape_control_character(ch: char) -> String {
20 match ch {
21 '\u{0008}' => "\\b".to_string(),
22 '\u{000C}' => "\\f".to_string(),
23 '\n' => "\\n".to_string(),
24 '\r' => "\\r".to_string(),
25 '\t' => "\\t".to_string(),
26 _ => format!("\\u{:04x}", ch as u32),
27 }
28}
29
30pub fn repair_json(json: &str) -> String {
34 let mut repaired = String::with_capacity(json.len());
35 let mut in_string = false;
36 let chars: Vec<char> = json.chars().collect();
37 let len = chars.len();
38 let mut index = 0;
39
40 while index < len {
41 let ch = chars[index];
42
43 if !in_string {
44 repaired.push(ch);
45 if ch == '"' {
46 in_string = true;
47 }
48 index += 1;
49 continue;
50 }
51
52 if ch == '"' {
54 repaired.push(ch);
55 in_string = false;
56 index += 1;
57 continue;
58 }
59
60 if ch == '\\' {
61 if index + 1 >= len {
63 repaired.push_str("\\\\");
65 index += 1;
66 continue;
67 }
68
69 let next_ch = chars[index + 1];
70
71 if next_ch == 'u' {
72 let unicode_digits: String = chars[index + 2..std::cmp::min(index + 6, len)]
74 .iter()
75 .collect();
76 if unicode_digits.len() == 4
77 && unicode_digits.chars().all(|c| c.is_ascii_hexdigit())
78 {
79 repaired.push_str(&format!("\\u{}", unicode_digits));
80 index += 6;
81 continue;
82 }
83 }
84
85 if VALID_JSON_ESCAPES.contains(&next_ch) {
86 repaired.push('\\');
87 repaired.push(next_ch);
88 index += 2;
89 continue;
90 }
91
92 repaired.push_str("\\\\");
94 index += 1;
95 continue;
96 }
97
98 if is_control_character(ch) {
100 repaired.push_str(&escape_control_character(ch));
101 } else {
102 repaired.push(ch);
103 }
104 index += 1;
105 }
106
107 repaired
108}
109
110pub fn parse_json_with_repair<T: serde::de::DeserializeOwned>(
114 json: &str,
115) -> Result<T, serde_json::Error> {
116 match serde_json::from_str(json) {
117 Ok(result) => Ok(result),
118 Err(original_error) => {
119 let repaired = repair_json(json);
120 if repaired != json {
121 match serde_json::from_str(&repaired) {
122 Ok(result) => Ok(result),
123 Err(_) => Err(original_error),
124 }
125 } else {
126 Err(original_error)
127 }
128 }
129 }
130}
131
132pub fn parse_streaming_json<T: serde::de::DeserializeOwned + Default>(json: &str) -> T {
141 let trimmed = json.trim();
142 if trimmed.is_empty() {
143 return T::default();
144 }
145
146 if let Ok(result) = serde_json::from_str(trimmed) {
148 return result;
149 }
150
151 if let Ok(result) = parse_json_with_repair(trimmed) {
153 return result;
154 }
155
156 if let Some(result) = parse_partial_json(trimmed) {
158 return result;
159 }
160
161 let repaired = repair_json(trimmed);
163 if repaired != trimmed
164 && let Some(result) = parse_partial_json(&repaired)
165 {
166 return result;
167 }
168
169 T::default()
170}
171
172fn parse_partial_json<T: serde::de::DeserializeOwned>(json: &str) -> Option<T> {
175 let trimmed = json.trim();
177 if !trimmed.starts_with('{') && !trimmed.starts_with('[') {
178 return None;
179 }
180
181 let _close_char = if trimmed.starts_with('{') { '}' } else { ']' };
182 let _open_char = if trimmed.starts_with('{') { '{' } else { '[' };
183
184 let mut depth = 0;
186 let mut in_string = false;
187 let mut last_valid_close = None;
188 let bytes = trimmed.as_bytes();
189
190 for (i, &b) in bytes.iter().enumerate() {
191 if in_string {
192 if b == b'"' {
193 in_string = false;
194 } else if b == b'\\' {
195 continue;
197 }
198 continue;
199 }
200
201 match b {
202 b'"' => in_string = true,
203 b'{' | b'[' => depth += 1,
204 b'}' | b']' => {
205 depth -= 1;
206 if depth == 0 {
207 last_valid_close = Some(i);
208 }
209 }
210 _ => {}
211 }
212 }
213
214 if let Some(pos) = last_valid_close {
216 let candidate = &trimmed[..=pos];
217 if let Ok(result) = serde_json::from_str(candidate) {
218 return Some(result);
219 }
220 }
221
222 None
223}
224
225pub fn parse_sse_data<T: serde::de::DeserializeOwned + Default>(line: &str) -> Option<T> {
228 let line = line.trim();
229
230 if !line.starts_with("data: ") {
231 return None;
232 }
233
234 let data = &line[6..];
235
236 if data.is_empty() || data == "[DONE]" {
237 return None;
238 }
239
240 Some(parse_streaming_json(data))
241}
242
243pub fn extract_error_message(message: &AssistantMessage) -> String {
246 message
247 .error_message
248 .clone()
249 .unwrap_or_else(|| "Unknown error".to_string())
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use serde::Deserialize;
256
257 #[derive(Debug, Deserialize, PartialEq, Default)]
258 struct TestObj {
259 name: String,
260 value: Option<i64>,
261 }
262
263 #[test]
264 fn test_repair_json_valid() {
265 let json = r#"{"name": "test"}"#;
266 assert_eq!(repair_json(json), json);
267 }
268
269 #[test]
270 fn test_repair_json_control_chars() {
271 let json = "{\"name\": \"hello\nworld\"}";
272 let repaired = repair_json(json);
273 assert!(repaired.contains("\\n"));
274 assert!(!repaired.contains("hello\nworld"));
275 }
276
277 #[test]
278 fn test_repair_json_tab() {
279 let json = "{\"name\": \"hello\tworld\"}";
280 let repaired = repair_json(json);
281 assert!(repaired.contains("\\t"));
282 }
283
284 #[test]
285 fn test_repair_json_invalid_escape() {
286 let json = r#"{"name": "hello\qworld"}"#;
287 let repaired = repair_json(json);
288 assert!(repaired.contains("\\\\q") || repaired.contains(r#"\\q"#));
289 }
290
291 #[test]
292 fn test_repair_json_trailing_backslash() {
293 let json = r#"{"name": "test\"#;
294 let repaired = repair_json(json);
295 assert!(repaired.contains("\\\\"));
296 }
297
298 #[test]
299 fn test_repair_json_valid_escapes_preserved() {
300 let json = r#"{"name": "hello\nworld"}"#;
301 let repaired = repair_json(json);
302 assert_eq!(repaired, json);
303 }
304
305 #[test]
306 fn test_repair_json_unicode_escape_preserved() {
307 let json = r#"{"name": "\u0041"}"#;
308 let repaired = repair_json(json);
309 assert_eq!(repaired, json);
310 }
311
312 #[test]
313 fn test_parse_json_with_repair_valid() {
314 let result: TestObj = parse_json_with_repair(r#"{"name": "test", "value": 42}"#).unwrap();
315 assert_eq!(result.name, "test");
316 assert_eq!(result.value, Some(42));
317 }
318
319 #[test]
320 fn test_parse_json_with_repair_control_chars() {
321 let json = "{\"name\": \"hello\nworld\"}";
322 let result: TestObj = parse_json_with_repair(json).unwrap();
323 assert_eq!(result.name, "hello\nworld");
324 }
325
326 #[test]
327 fn test_parse_streaming_json_valid() {
328 let result: TestObj = parse_streaming_json(r#"{"name": "test"}"#);
329 assert_eq!(result.name, "test");
330 }
331
332 #[test]
333 fn test_parse_streaming_json_empty() {
334 let result: TestObj = parse_streaming_json("");
335 assert_eq!(result, TestObj::default());
336 }
337
338 #[test]
339 fn test_parse_streaming_json_whitespace() {
340 let result: TestObj = parse_streaming_json(" ");
341 assert_eq!(result, TestObj::default());
342 }
343
344 #[test]
345 fn test_parse_streaming_json_partial() {
346 let result: TestObj = parse_streaming_json(r#"{"name": "test"}, "extra""#);
347 assert_eq!(result.name, "test");
348 }
349
350 #[test]
351 fn test_parse_sse_data_valid() {
352 let result: TestObj = parse_sse_data(r#"data: {"name": "test"}"#).unwrap();
353 assert_eq!(result.name, "test");
354 }
355
356 #[test]
357 fn test_parse_sse_data_done() {
358 let result: Option<TestObj> = parse_sse_data("data: [DONE]");
359 assert!(result.is_none());
360 }
361
362 #[test]
363 fn test_parse_sse_data_not_data_line() {
364 let result: Option<TestObj> = parse_sse_data("event: message");
365 assert!(result.is_none());
366 }
367
368 #[test]
369 fn test_parse_sse_data_empty_data() {
370 let result: Option<TestObj> = parse_sse_data("data: ");
371 assert!(result.is_none());
372 }
373
374 #[test]
375 fn test_escape_control_character_special() {
376 assert_eq!(escape_control_character('\n'), "\\n");
377 assert_eq!(escape_control_character('\r'), "\\r");
378 assert_eq!(escape_control_character('\t'), "\\t");
379 assert_eq!(escape_control_character('\u{0008}'), "\\b");
380 assert_eq!(escape_control_character('\u{000C}'), "\\f");
381 }
382
383 #[test]
384 fn test_escape_control_character_generic() {
385 assert_eq!(escape_control_character('\u{0001}'), "\\u0001");
386 assert_eq!(escape_control_character('\u{001F}'), "\\u001f");
387 }
388}