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