lightstreamer_rs/utils/
util.rs

1use std::sync::Arc;
2use tokio::sync::Notify;
3#[cfg(windows)]
4use tracing::error;
5use tracing::info;
6
7/// Clean the message from newlines and carriage returns and convert it to lowercase. Also remove all brackets.
8pub fn clean_message(text: &str) -> String {
9    let mut result = String::new();
10    let mut inside_braces = false;
11
12    for part in text.split_inclusive(&['{', '}']) {
13        if part.starts_with('{') && part.ends_with('}') {
14            // Part is fully inside braces
15            inside_braces = true;
16            result.push_str(part);
17        } else if inside_braces {
18            // We're processing a segment after an opening brace
19            inside_braces = false;
20            result.push_str(part);
21        } else {
22            // Process the part outside braces
23            let chars_to_replace = ['\n', '\r']; // Using an array of chars to replace
24            result.push_str(&part.replace(chars_to_replace, "").to_lowercase());
25        }
26    }
27
28    result
29}
30
31/// Parses a comma-separated string input into a vector of string slices (`Vec<&str>`).
32///
33/// This function supports skipping commas inside nested curly braces `{}`. It correctly handles
34/// nested structures, ensuring that commas within curly braces are not treated as delimiters.
35///
36/// # Parameters
37/// - `input`: A string slice (`&str`) containing comma-separated values, potentially with nested curly braces.
38///
39/// # Returns
40/// A `Vec<&str>` containing trimmed substrings split by commas outside of curly braces.
41///
42/// # Behavior
43/// - Commas outside of curly braces `{}` are treated as delimiters.
44/// - Commas inside curly braces are ignored for splitting purposes.
45/// - Leading and trailing whitespace around substrings are trimmed.
46/// - Empty substrings (those consisting solely of whitespace) are ignored.
47///
48/// # Caveats
49/// - The function requires matched curly braces `{}`. If the input contains unmatched curly braces,
50///   the function may produce unexpected results.
51///
52/// # Panics
53/// This function does not explicitly panic, but improper manipulation of indices or unmatched
54/// braces could lead to unintended behavior.
55///
56/// # Errors in Current Code:
57/// - There is a bug in the code where `arguments.push(*slice)` is used. The dereference operator
58///   (`*`) is invalid for string slices. It should be `arguments.push(slice)`.
59/// - Recommend fixing this bug by removing the dereference operator for proper functionality.
60pub fn parse_arguments(input: &str) -> Vec<&str> {
61    let mut arguments = Vec::new();
62    let mut start = 0;
63    let mut in_brackets = 0; // Tracks nesting level for curly braces
64
65    for (i, c) in input.char_indices() {
66        match c {
67            '{' => in_brackets += 1,
68            '}' => in_brackets -= 1,
69            ',' if in_brackets == 0 => {
70                // Outside of brackets, treat comma as a delimiter
71                let slice = &input[start..i].trim();
72                if !slice.is_empty() {
73                    arguments.push(*slice); // Dereference slice here
74                }
75                start = i + 1;
76            }
77            _ => {}
78        }
79    }
80
81    // Push the final argument if it's not empty
82    if start < input.len() {
83        let slice = &input[start..].trim();
84        if !slice.is_empty() {
85            arguments.push(*slice); // Dereference slice here
86        }
87    }
88
89    arguments
90}
91
92/// Sets up cross-platform signal handling for graceful shutdown.
93/// On Unix systems, handles SIGINT and SIGTERM signals.
94/// On Windows, handles Ctrl+C signal.
95///
96/// # Arguments
97///
98/// * `shutdown_signal` - `Arc<Notify>` to signal shutdown to other parts of the application.
99///
100/// # Panics
101///
102/// The function panics if it fails to create the signal handlers.
103///
104pub async fn setup_signal_hook(shutdown_signal: Arc<Notify>) {
105    #[cfg(unix)]
106    {
107        use tokio::signal::unix::{SignalKind, signal};
108        let mut sigint = signal(SignalKind::interrupt()).expect("Failed to create SIGINT handler");
109        let mut sigterm =
110            signal(SignalKind::terminate()).expect("Failed to create SIGTERM handler");
111
112        tokio::spawn(async move {
113            tokio::select! {
114                _ = sigint.recv() => {
115                    info!("Received SIGINT signal");
116                    shutdown_signal.notify_one();
117                }
118                _ = sigterm.recv() => {
119                    info!("Received SIGTERM signal");
120                    shutdown_signal.notify_one();
121                }
122            }
123        });
124    }
125
126    #[cfg(windows)]
127    {
128        use tokio::signal;
129        tokio::spawn(async move {
130            match signal::ctrl_c().await {
131                Ok(()) => {
132                    info!("Received Ctrl+C signal");
133                    shutdown_signal.notify_one();
134                }
135                Err(err) => {
136                    error!("Failed to listen for Ctrl+C: {}", err);
137                }
138            }
139        });
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    mod clean_message_tests {
148        use super::*;
149
150        #[test]
151        fn test_clean_message_basic() {
152            let text = "Hello\nWorld";
153            let result = clean_message(text);
154            assert_eq!(result, "helloworld");
155        }
156
157        #[test]
158        fn test_clean_message_with_partial_braces() {
159            // This tests the case where a part starts with '{' but doesn't end with '}'
160            let text = "{partial brace content} followed by text";
161            let result = clean_message(text);
162            assert_eq!(result, "{partial brace content} followed by text");
163        }
164
165        #[test]
166        fn test_clean_message_with_ending_brace() {
167            // This tests the case where a part ends with '}' but doesn't start with '{'
168            let text = "text followed by {partial brace content}";
169            let result = clean_message(text);
170            assert_eq!(result, "text followed by {partial brace content}");
171        }
172
173        #[test]
174        fn test_clean_message_with_carriage_return() {
175            let text = "Hello\r\nWorld";
176            let result = clean_message(text);
177            assert_eq!(result, "helloworld");
178        }
179
180        #[test]
181        fn test_clean_message_lowercase_conversion() {
182            let text = "Hello WORLD";
183            let result = clean_message(text);
184            assert_eq!(result, "hello world");
185        }
186
187        #[test]
188        fn test_clean_message_empty_string() {
189            let text = "";
190            let result = clean_message(text);
191            assert_eq!(result, "");
192        }
193
194        #[test]
195        fn test_clean_message_preserve_braces_content() {
196            let text = "Message with {Preserved\nContent} and not preserved\nContent";
197            let result = clean_message(text);
198            assert_eq!(
199                result,
200                "message with {preservedcontent} and not preservedcontent"
201            );
202        }
203
204        #[test]
205        fn test_clean_message_nested_braces() {
206            let text = "Message with {Outer{Inner\nContent}Outer} and regular\nContent";
207            let result = clean_message(text);
208            assert_eq!(
209                result,
210                "message with {outer{innercontent}outer} and regularcontent"
211            );
212        }
213
214        #[test]
215        fn test_clean_message_unbalanced_braces() {
216            let text = "Message with {Unbalanced and regular\nContent";
217            let result = clean_message(text);
218            assert_eq!(result, "message with {unbalanced and regularcontent");
219        }
220
221        #[test]
222        fn test_clean_message_protocol_example() {
223            // Typical TLCP message examples
224            let text = "CONOK,S8f4aec42c3c14ad0,50000,5000,*\r\n";
225            let result = clean_message(text);
226            assert_eq!(result, "conok,s8f4aec42c3c14ad0,50000,5000,*");
227
228            let text = "PROBE\r\n";
229            let result = clean_message(text);
230            assert_eq!(result, "probe");
231        }
232    }
233
234    mod parse_arguments_tests {
235        use super::*;
236
237        #[test]
238        fn test_parse_arguments_basic() {
239            let input = "arg1,arg2,arg3";
240            let result = parse_arguments(input);
241            assert_eq!(result, vec!["arg1", "arg2", "arg3"]);
242        }
243
244        #[test]
245        fn test_parse_arguments_empty_string() {
246            let input = "";
247            let result = parse_arguments(input);
248            assert_eq!(result, Vec::<&str>::new());
249        }
250
251        #[test]
252        fn test_parse_arguments_single_argument() {
253            let input = "arg1";
254            let result = parse_arguments(input);
255            assert_eq!(result, vec!["arg1"]);
256        }
257
258        #[test]
259        fn test_parse_arguments_with_whitespace() {
260            let input = " arg1 , arg2 , arg3 ";
261            let result = parse_arguments(input);
262            assert_eq!(result, vec!["arg1", "arg2", "arg3"]);
263        }
264
265        #[test]
266        fn test_parse_arguments_empty_arguments() {
267            let input = "arg1,,arg3";
268            let result = parse_arguments(input);
269            assert_eq!(result, vec!["arg1", "arg3"]);
270        }
271
272        #[test]
273        fn test_parse_arguments_with_braces() {
274            let input = "arg1,{inner1,inner2},arg3";
275            let result = parse_arguments(input);
276            assert_eq!(result, vec!["arg1", "{inner1,inner2}", "arg3"]);
277        }
278
279        #[test]
280        fn test_parse_arguments_nested_braces() {
281            let input = "arg1,{outer{inner1,inner2}outer},arg3";
282            let result = parse_arguments(input);
283            assert_eq!(result, vec!["arg1", "{outer{inner1,inner2}outer}", "arg3"]);
284        }
285
286        #[test]
287        fn test_parse_arguments_unbalanced_braces() {
288            let input = "arg1,{unbalanced,arg3";
289            let result = parse_arguments(input);
290            // Even with unbalanced braces, we expect it to treat everything inside as one argument
291            assert_eq!(result, vec!["arg1", "{unbalanced,arg3"]);
292        }
293
294        #[test]
295        fn test_parse_arguments_protocol_examples() {
296            // TLCP protocol message example arguments
297            let input = "CONOK,S8f4aec42c3c14ad0,50000,5000,*";
298            let result = parse_arguments(input);
299            assert_eq!(
300                result,
301                vec!["CONOK", "S8f4aec42c3c14ad0", "50000", "5000", "*"]
302            );
303
304            let input = "u,1,1,a|b|c";
305            let result = parse_arguments(input);
306            assert_eq!(result, vec!["u", "1", "1", "a|b|c"]);
307        }
308    }
309}