1use std::sync::Arc;
2use tokio::sync::Notify;
3#[cfg(windows)]
4use tracing::error;
5use tracing::info;
6
7pub fn clean_message(text: &str) -> String {
17 let chars_to_replace = ['\n', '\r'];
18 let stripped = text.replace(chars_to_replace, "");
19
20 let mut in_brackets: usize = 0;
22 let mut first_comma: Option<usize> = None;
23 for (i, c) in stripped.char_indices() {
24 match c {
25 '{' => in_brackets += 1,
26 '}' => in_brackets = in_brackets.saturating_sub(1),
27 ',' if in_brackets == 0 => {
28 first_comma = Some(i);
29 break;
30 }
31 _ => {}
32 }
33 }
34
35 match first_comma {
36 Some(pos) => {
37 let mut result = String::with_capacity(stripped.len());
39 result.push_str(&stripped[..pos].to_lowercase());
40 result.push_str(&stripped[pos..]);
41 result
42 }
43 None => {
44 stripped.to_lowercase()
46 }
47 }
48}
49
50pub fn parse_arguments(input: &str) -> Vec<&str> {
80 let mut arguments = Vec::new();
81 let mut start = 0;
82 let mut in_brackets = 0; for (i, c) in input.char_indices() {
85 match c {
86 '{' => in_brackets += 1,
87 '}' => in_brackets -= 1,
88 ',' if in_brackets == 0 => {
89 let slice = &input[start..i].trim();
91 if !slice.is_empty() {
92 arguments.push(*slice); }
94 start = i + 1;
95 }
96 _ => {}
97 }
98 }
99
100 if start < input.len() {
102 let slice = &input[start..].trim();
103 if !slice.is_empty() {
104 arguments.push(*slice); }
106 }
107
108 arguments
109}
110
111pub async fn setup_signal_hook(shutdown_signal: Arc<Notify>) {
124 #[cfg(unix)]
125 {
126 use tokio::signal::unix::{SignalKind, signal};
127 let mut sigint = signal(SignalKind::interrupt()).expect("Failed to create SIGINT handler");
128 let mut sigterm =
129 signal(SignalKind::terminate()).expect("Failed to create SIGTERM handler");
130
131 tokio::spawn(async move {
132 tokio::select! {
133 _ = sigint.recv() => {
134 info!("Received SIGINT signal");
135 shutdown_signal.notify_one();
136 }
137 _ = sigterm.recv() => {
138 info!("Received SIGTERM signal");
139 shutdown_signal.notify_one();
140 }
141 }
142 });
143 }
144
145 #[cfg(windows)]
146 {
147 use tokio::signal;
148 tokio::spawn(async move {
149 match signal::ctrl_c().await {
150 Ok(()) => {
151 info!("Received Ctrl+C signal");
152 shutdown_signal.notify_one();
153 }
154 Err(err) => {
155 error!("Failed to listen for Ctrl+C: {}", err);
156 }
157 }
158 });
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 mod clean_message_tests {
167 use super::*;
168
169 #[test]
170 fn test_clean_message_basic() {
171 let text = "Hello\nWorld";
173 let result = clean_message(text);
174 assert_eq!(result, "helloworld");
175 }
176
177 #[test]
178 fn test_clean_message_with_partial_braces() {
179 let text = "{partial brace content} followed by text";
181 let result = clean_message(text);
182 assert_eq!(result, "{partial brace content} followed by text");
183 }
184
185 #[test]
186 fn test_clean_message_with_ending_brace() {
187 let text = "text followed by {partial brace content}";
189 let result = clean_message(text);
190 assert_eq!(result, "text followed by {partial brace content}");
191 }
192
193 #[test]
194 fn test_clean_message_with_carriage_return() {
195 let text = "Hello\r\nWorld";
197 let result = clean_message(text);
198 assert_eq!(result, "helloworld");
199 }
200
201 #[test]
202 fn test_clean_message_lowercase_conversion() {
203 let text = "Hello WORLD";
205 let result = clean_message(text);
206 assert_eq!(result, "hello world");
207 }
208
209 #[test]
210 fn test_clean_message_empty_string() {
211 let text = "";
212 let result = clean_message(text);
213 assert_eq!(result, "");
214 }
215
216 #[test]
217 fn test_clean_message_preserve_braces_content() {
218 let text = "Message with {Preserved\nContent} and not preserved\nContent";
221 let result = clean_message(text);
222 assert_eq!(
223 result,
224 "message with {preservedcontent} and not preservedcontent"
225 );
226 }
227
228 #[test]
229 fn test_clean_message_nested_braces() {
230 let text = "Message with {Outer{Inner\nContent}Outer} and regular\nContent";
232 let result = clean_message(text);
233 assert_eq!(
234 result,
235 "message with {outer{innercontent}outer} and regularcontent"
236 );
237 }
238
239 #[test]
240 fn test_clean_message_unbalanced_braces() {
241 let text = "Message with {Unbalanced and regular\nContent";
243 let result = clean_message(text);
244 assert_eq!(result, "message with {unbalanced and regularcontent");
245 }
246
247 #[test]
248 fn test_clean_message_protocol_example() {
249 let text = "CONOK,S8f4aec42c3c14ad0,50000,5000,*\r\n";
251 let result = clean_message(text);
252 assert_eq!(result, "conok,S8f4aec42c3c14ad0,50000,5000,*");
253
254 let text = "PROBE\r\n";
255 let result = clean_message(text);
256 assert_eq!(result, "probe");
257 }
258
259 #[test]
260 fn test_clean_message_update_preserves_field_values() {
261 let text = "U,1,1,DEAL|1.32|#|^P\r\n";
263 let result = clean_message(text);
264 assert_eq!(result, "u,1,1,DEAL|1.32|#|^P");
265 }
266
267 #[test]
268 fn test_clean_message_update_preserves_caret_commands() {
269 let text = "U,1,1,^5|CLOSED|-0.5\r\n";
270 let result = clean_message(text);
271 assert_eq!(result, "u,1,1,^5|CLOSED|-0.5");
272 }
273
274 #[test]
275 fn test_clean_message_reqerr_preserves_details() {
276 let text = "REQERR,21,InvalidField\r\n";
277 let result = clean_message(text);
278 assert_eq!(result, "reqerr,21,InvalidField");
279 }
280 }
281
282 mod parse_arguments_tests {
283 use super::*;
284
285 #[test]
286 fn test_parse_arguments_basic() {
287 let input = "arg1,arg2,arg3";
288 let result = parse_arguments(input);
289 assert_eq!(result, vec!["arg1", "arg2", "arg3"]);
290 }
291
292 #[test]
293 fn test_parse_arguments_empty_string() {
294 let input = "";
295 let result = parse_arguments(input);
296 assert_eq!(result, Vec::<&str>::new());
297 }
298
299 #[test]
300 fn test_parse_arguments_single_argument() {
301 let input = "arg1";
302 let result = parse_arguments(input);
303 assert_eq!(result, vec!["arg1"]);
304 }
305
306 #[test]
307 fn test_parse_arguments_with_whitespace() {
308 let input = " arg1 , arg2 , arg3 ";
309 let result = parse_arguments(input);
310 assert_eq!(result, vec!["arg1", "arg2", "arg3"]);
311 }
312
313 #[test]
314 fn test_parse_arguments_empty_arguments() {
315 let input = "arg1,,arg3";
316 let result = parse_arguments(input);
317 assert_eq!(result, vec!["arg1", "arg3"]);
318 }
319
320 #[test]
321 fn test_parse_arguments_with_braces() {
322 let input = "arg1,{inner1,inner2},arg3";
323 let result = parse_arguments(input);
324 assert_eq!(result, vec!["arg1", "{inner1,inner2}", "arg3"]);
325 }
326
327 #[test]
328 fn test_parse_arguments_nested_braces() {
329 let input = "arg1,{outer{inner1,inner2}outer},arg3";
330 let result = parse_arguments(input);
331 assert_eq!(result, vec!["arg1", "{outer{inner1,inner2}outer}", "arg3"]);
332 }
333
334 #[test]
335 fn test_parse_arguments_unbalanced_braces() {
336 let input = "arg1,{unbalanced,arg3";
337 let result = parse_arguments(input);
338 assert_eq!(result, vec!["arg1", "{unbalanced,arg3"]);
340 }
341
342 #[test]
343 fn test_parse_arguments_protocol_examples() {
344 let input = "CONOK,S8f4aec42c3c14ad0,50000,5000,*";
346 let result = parse_arguments(input);
347 assert_eq!(
348 result,
349 vec!["CONOK", "S8f4aec42c3c14ad0", "50000", "5000", "*"]
350 );
351
352 let input = "u,1,1,a|b|c";
353 let result = parse_arguments(input);
354 assert_eq!(result, vec!["u", "1", "1", "a|b|c"]);
355 }
356 }
357}