zerobox_utils_string/
json.rs1use std::io;
5
6use serde::Serialize;
7
8struct AsciiJsonFormatter;
9
10impl serde_json::ser::Formatter for AsciiJsonFormatter {
11 fn write_string_fragment<W>(&mut self, writer: &mut W, fragment: &str) -> io::Result<()>
14 where
15 W: ?Sized + io::Write,
16 {
17 let mut start = 0;
18 for (index, ch) in fragment.char_indices() {
19 if ch.is_ascii() {
20 continue;
21 }
22
23 if start < index {
24 writer.write_all(&fragment.as_bytes()[start..index])?;
25 }
26
27 let mut utf16 = [0; 2];
28 for code_unit in ch.encode_utf16(&mut utf16) {
29 write!(writer, "\\u{code_unit:04x}")?;
30 }
31 start = index + ch.len_utf8();
32 }
33
34 if start < fragment.len() {
35 writer.write_all(&fragment.as_bytes()[start..])?;
36 }
37
38 Ok(())
39 }
40}
41
42pub fn to_ascii_json_string<T>(value: &T) -> serde_json::Result<String>
47where
48 T: Serialize + ?Sized,
49{
50 let mut bytes = Vec::new();
51 let mut serializer = serde_json::Serializer::with_formatter(&mut bytes, AsciiJsonFormatter);
52 value.serialize(&mut serializer)?;
53 String::from_utf8(bytes)
54 .map_err(|err| serde_json::Error::io(io::Error::new(io::ErrorKind::InvalidData, err)))
55}
56
57#[cfg(test)]
58mod tests {
59 use std::collections::BTreeMap;
60
61 use pretty_assertions::assert_eq;
62 use serde::Serialize;
63 use serde::ser::SerializeStruct;
64 use serde_json::Value;
65 use serde_json::json;
66
67 use super::to_ascii_json_string;
68
69 #[test]
70 fn to_ascii_json_string_escapes_non_ascii_strings() {
71 struct TestPayload;
72
73 impl Serialize for TestPayload {
74 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75 where
76 S: serde::Serializer,
77 {
78 let workspaces = BTreeMap::from([("/tmp/東京", TestWorkspace)]);
79 let mut state = serializer.serialize_struct("TestPayload", 1)?;
80 state.serialize_field("workspaces", &workspaces)?;
81 state.end()
82 }
83 }
84
85 struct TestWorkspace;
86
87 impl Serialize for TestWorkspace {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: serde::Serializer,
91 {
92 let mut state = serializer.serialize_struct("TestWorkspace", 2)?;
93 state.serialize_field("label", "Agentlarım")?;
94 state.serialize_field("emoji", "🚀")?;
95 state.end()
96 }
97 }
98
99 let value = TestPayload;
100 let expected_value = json!({
101 "workspaces": {
102 "/tmp/東京": {
103 "label": "Agentlarım",
104 "emoji": "🚀"
105 }
106 }
107 });
108
109 let serialized = to_ascii_json_string(&value).expect("serialize ascii json");
110
111 assert_eq!(
112 serialized,
113 r#"{"workspaces":{"/tmp/\u6771\u4eac":{"label":"Agentlar\u0131m","emoji":"\ud83d\ude80"}}}"#
114 );
115 assert!(serialized.is_ascii());
116 assert!(!serialized.contains("東京"));
117 assert!(!serialized.contains("Agentlarım"));
118 assert!(!serialized.contains("🚀"));
119 let parsed: Value = serde_json::from_str(&serialized).expect("serialized json");
120 assert_eq!(parsed, expected_value);
121 }
122}