ai_agent/
cli_ndjson_safe_stringify.rs1#![allow(dead_code)]
13
14fn escape_js_line_terminators(json: &str) -> String {
21 json.replace('\u{2028}', "\\u2028")
22 .replace('\u{2029}', "\\u2029")
23}
24
25pub fn serialize_to_ndjson<T: serde::Serialize>(value: &T) -> Result<String, serde_json::Error> {
27 let json = serde_json::to_string(value)?;
28 Ok(escape_js_line_terminators(&json))
29}
30
31pub fn serialize_to_ndjson_safe(value: &serde_json::Value) -> String {
33 let json = serde_json::to_string(value).unwrap_or_default();
34 escape_js_line_terminators(&json)
35}
36
37pub fn escape_unicode_line_separators(text: &str) -> String {
40 escape_js_line_terminators(text)
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn test_escape_u2028() {
49 let json = r#""hello
world""#;
50 let input = "hello\u{2028}world";
52 let quoted = format!("\"{}\"", input);
53 let escaped = escape_js_line_terminators("ed);
54 assert!(escaped.contains("\\u2028"));
55 assert!(!escaped.contains('\u{2028}'));
56 }
57
58 #[test]
59 fn test_escape_u2029() {
60 let input = "hello\u{2029}world";
61 let quoted = format!("\"{}\"", input);
62 let escaped = escape_js_line_terminators("ed);
63 assert!(escaped.contains("\\u2029"));
64 assert!(!escaped.contains('\u{2029}'));
65 }
66
67 #[test]
68 fn test_escape_both() {
69 let input = "\u{2028}start\u{2029}middle\u{2028}end";
70 let escaped = escape_js_line_terminators(input);
71 assert_eq!(escaped, "\\u2028start\\u2029middle\\u2028end");
72 }
73
74 #[test]
75 fn test_no_escape_needed() {
76 let input = r#""hello world""#;
77 let escaped = escape_js_line_terminators(input);
78 assert_eq!(escaped, r#""hello world""#);
79 }
80
81 #[test]
82 fn test_serialize_to_ndjson_safe() {
83 let value = serde_json::json!("test\u{2028}value\u{2029}end");
84 let result = serialize_to_ndjson_safe(&value);
85 assert!(result.contains("\\u2028"));
86 assert!(result.contains("\\u2029"));
87 assert!(serde_json::from_str::<serde_json::Value>(&result).is_ok());
89 }
90
91 #[test]
92 fn test_serialize_to_ndjson_with_struct() {
93 #[derive(serde::Serialize)]
94 struct Test {
95 text: String,
96 }
97 let t = Test {
98 text: "line\u{2028}separator".to_string(),
99 };
100 let result = serialize_to_ndjson(&t).unwrap();
101 assert!(result.contains("\\u2028"));
102 }
103
104 #[test]
105 fn test_escape_unicode_line_separators() {
106 let input = "normal\nline\u{2028}separator\u{2029}paragraph";
107 let escaped = escape_unicode_line_separators(&input);
108 assert!(escaped.contains('\n'));
110 assert!(escaped.contains("\\u2028"));
112 assert!(escaped.contains("\\u2029"));
113 }
114
115 #[test]
116 fn test_roundtrip_parsing() {
117 let original = "hello\u{2028}world\u{2029}!";
119 let value = serde_json::json!(original);
120 let escaped = serialize_to_ndjson_safe(&value);
121 let parsed = serde_json::from_str::<serde_json::Value>(&escaped).unwrap();
122 assert_eq!(parsed.as_str().unwrap(), original);
123 }
124}