1extern crate alloc;
2
3use alloc::{format, string::String, vec::Vec};
4
5use serde::Serialize;
6
7use serde_json::{json, Value};
8
9const MAX_STRING_LEN: usize = 150;
10
11#[derive(Debug)]
13struct SubstringSpec {
14 start_index: usize,
16 length: usize,
18}
19
20impl SubstringSpec {
21 fn new(start_index: usize, length: usize) -> Self {
23 Self {
24 start_index,
25 length,
26 }
27 }
28}
29
30pub fn json_pretty_print<T>(value: &T) -> serde_json::Result<String>
37where
38 T: ?Sized + Serialize,
39{
40 let mut json_value = json!(value);
41 shorten_string_field(&mut json_value);
42
43 serde_json::to_string_pretty(&json_value)
44}
45
46fn find_hex_strings_longer_than(string: &str, max_len: usize) -> Vec<SubstringSpec> {
49 let mut ranges_to_remove = Vec::new();
50 let mut start_index = 0;
51 let mut contiguous_hex_count = 0;
52
53 for (index, char) in string.char_indices() {
55 if char.is_ascii_hexdigit() {
56 if contiguous_hex_count == 0 {
57 start_index = index;
59 }
60 contiguous_hex_count += 1;
61 } else if contiguous_hex_count != 0 {
62 if contiguous_hex_count > max_len {
64 ranges_to_remove.push(SubstringSpec::new(start_index, contiguous_hex_count));
65 }
66 contiguous_hex_count = 0;
67 }
68 }
69 if contiguous_hex_count > max_len {
71 ranges_to_remove.push(SubstringSpec::new(start_index, contiguous_hex_count));
72 }
73 ranges_to_remove
74}
75
76fn shorten_string_field(value: &mut Value) {
77 match value {
78 Value::String(string) => {
79 find_hex_strings_longer_than(string, MAX_STRING_LEN)
82 .into_iter()
83 .rev()
84 .for_each(
85 |SubstringSpec {
86 start_index,
87 length,
88 }| {
89 let range = start_index..(start_index + length);
90 string.replace_range(range, &format!("[{} hex chars]", length));
91 },
92 )
93 }
94 Value::Array(values) => {
95 for value in values {
96 shorten_string_field(value);
97 }
98 }
99 Value::Object(map) => {
100 for map_value in map.values_mut() {
101 shorten_string_field(map_value);
102 }
103 }
104 Value::Null | Value::Bool(_) | Value::Number(_) => {}
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 fn hex_string(length: usize) -> String {
113 "0123456789abcdef".chars().cycle().take(length).collect()
114 }
115
116 impl PartialEq<(usize, usize)> for SubstringSpec {
117 fn eq(&self, other: &(usize, usize)) -> bool {
118 self.start_index == other.0 && self.length == other.1
119 }
120 }
121
122 #[test]
123 fn finds_hex_strings_longer_than() {
124 const TESTING_LEN: usize = 3;
125
126 let input = "01234";
127 let expected = vec![(0, 5)];
128 let actual = find_hex_strings_longer_than(input, TESTING_LEN);
129 assert_eq!(actual, expected);
130
131 let input = "01234-0123";
132 let expected = vec![(0, 5), (6, 4)];
133 let actual = find_hex_strings_longer_than(input, TESTING_LEN);
134 assert_eq!(actual, expected);
135
136 let input = "012-34-0123";
137 let expected = vec![(7, 4)];
138 let actual = find_hex_strings_longer_than(input, TESTING_LEN);
139 assert_eq!(actual, expected);
140
141 let input = "012-34-01-23";
142 let expected: Vec<(usize, usize)> = vec![];
143 let actual = find_hex_strings_longer_than(input, TESTING_LEN);
144 assert_eq!(actual, expected);
145
146 let input = "0";
147 let expected: Vec<(usize, usize)> = vec![];
148 let actual = find_hex_strings_longer_than(input, TESTING_LEN);
149 assert_eq!(actual, expected);
150
151 let input = "";
152 let expected: Vec<(usize, usize)> = vec![];
153 let actual = find_hex_strings_longer_than(input, TESTING_LEN);
154 assert_eq!(actual, expected);
155 }
156
157 #[test]
158 fn respects_length() {
159 let input = "I like beef";
160 let expected = vec![(7, 4)];
161 let actual = find_hex_strings_longer_than(input, 3);
162 assert_eq!(actual, expected);
163
164 let input = "I like beef";
165 let expected: Vec<(usize, usize)> = vec![];
166 let actual = find_hex_strings_longer_than(input, 1000);
167 assert_eq!(actual, expected);
168 }
169
170 #[test]
171 fn should_shorten_long_strings() {
172 let max_unshortened_hex_string = hex_string(MAX_STRING_LEN);
173 let long_hex_string = hex_string(MAX_STRING_LEN + 1);
174 let long_non_hex_string: String = "g".repeat(MAX_STRING_LEN + 1);
175 let long_hex_substring = format!("a-{}-b", hex_string(MAX_STRING_LEN + 1));
176 let multiple_long_hex_substrings =
177 format!("a: {0}, b: {0}, c: {0}", hex_string(MAX_STRING_LEN + 1));
178
179 let mut long_strings: Vec<String> = vec![];
180 for i in 1..=5 {
181 long_strings.push("a".repeat(MAX_STRING_LEN + i));
182 }
183 let value = json!({
184 "field_1": Option::<usize>::None,
185 "field_2": true,
186 "field_3": 123,
187 "field_4": max_unshortened_hex_string,
188 "field_5": ["short string value", long_hex_string],
189 "field_6": {
190 "f1": Option::<usize>::None,
191 "f2": false,
192 "f3": -123,
193 "f4": long_non_hex_string,
194 "f5": ["short string value", long_hex_substring],
195 "f6": {
196 "final long string": multiple_long_hex_substrings
197 }
198 }
199 });
200
201 let expected = r#"{
202 "field_1": null,
203 "field_2": true,
204 "field_3": 123,
205 "field_4": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345",
206 "field_5": [
207 "short string value",
208 "[151 hex chars]"
209 ],
210 "field_6": {
211 "f1": null,
212 "f2": false,
213 "f3": -123,
214 "f4": "ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg",
215 "f5": [
216 "short string value",
217 "a-[151 hex chars]-b"
218 ],
219 "f6": {
220 "final long string": "a: [151 hex chars], b: [151 hex chars], c: [151 hex chars]"
221 }
222 }
223}"#;
224
225 let output = json_pretty_print(&value).unwrap();
226 assert_eq!(
227 output, expected,
228 "Actual:\n{}\nExpected:\n{}\n",
229 output, expected
230 );
231 }
232
233 #[test]
234 fn should_not_modify_short_strings() {
235 let max_string: String = "a".repeat(MAX_STRING_LEN);
236 let value = json!({
237 "field_1": Option::<usize>::None,
238 "field_2": true,
239 "field_3": 123,
240 "field_4": max_string,
241 "field_5": [
242 "short string value",
243 "another short string"
244 ],
245 "field_6": {
246 "f1": Option::<usize>::None,
247 "f2": false,
248 "f3": -123,
249 "f4": "short",
250 "f5": [
251 "short string value",
252 "another short string"
253 ],
254 "f6": {
255 "final string": "the last short string"
256 }
257 }
258 });
259
260 let expected = serde_json::to_string_pretty(&value).unwrap();
261 let output = json_pretty_print(&value).unwrap();
262 assert_eq!(
263 output, expected,
264 "Actual:\n{}\nExpected:\n{}\n",
265 output, expected
266 );
267 }
268
269 #[test]
270 fn regression_1456() {
272 let long_string = r#"state query failed: ValueNotFound("Failed to find base key at path: Key::Account(72698d4dc715a28347b15920b09b4f0f1d633be5a33f4686d06992415b0825e2)")"#;
273 assert_eq!(long_string.len(), 148);
274
275 let value = json!({
276 "code": -32003,
277 "message": long_string,
278 });
279
280 let expected = r#"{
281 "code": -32003,
282 "message": "state query failed: ValueNotFound(\"Failed to find base key at path: Key::Account(72698d4dc715a28347b15920b09b4f0f1d633be5a33f4686d06992415b0825e2)\")"
283}"#;
284
285 let output = json_pretty_print(&value).unwrap();
286 assert_eq!(
287 output, expected,
288 "Actual:\n{}\nExpected:\n{}\n",
289 output, expected
290 );
291 }
292}