1use std::{
10 path::PathBuf,
11 time::{Duration, Instant},
12};
13
14use anyhow::Result;
15use bytes::Bytes;
16use strip_ansi_escapes::strip;
17use unicode_width::UnicodeWidthStr as _;
18
19#[allow(clippy::unnecessary_wraps)]
25pub fn to_path_buf(path: &String) -> Result<PathBuf> {
26 Ok(PathBuf::from(path))
27}
28
29#[must_use]
31pub fn send_ts_ping(origin: Instant) -> [u8; 12] {
32 let ts = Instant::now().duration_since(origin);
33 let (ts1, ts2) = (ts.as_secs(), ts.subsec_nanos());
34 let mut ts = [0; 12];
35 ts[0..8].copy_from_slice(&ts1.to_be_bytes());
36 ts[8..12].copy_from_slice(&ts2.to_be_bytes());
37 ts
38}
39
40pub fn parse_ts_ping(bytes: &Bytes) -> Option<Duration> {
42 if bytes.len() == 12 {
43 let secs_bytes = <[u8; 8]>::try_from(&bytes[0..8]).unwrap_or([0; 8]);
44 let nanos_bytes = <[u8; 4]>::try_from(&bytes[8..12]).unwrap_or([0; 4]);
45 let secs = u64::from_be_bytes(secs_bytes);
46 let nanos = u32::from_be_bytes(nanos_bytes);
47 Some(Duration::new(secs, nanos))
48 } else {
49 None
50 }
51}
52
53#[allow(clippy::mut_mut)]
54pub(crate) fn until_err<T>(err: &mut &mut Result<()>, item: Result<T>) -> Option<T> {
55 match item {
56 Ok(item) => Some(item),
57 Err(e) => {
58 **err = Err(e);
59 None
60 }
61 }
62}
63
64#[must_use]
66pub fn clean_output_string(data: &str) -> (String, usize) {
67 let data = data.replace('\t', " ");
68 let data = data.replace('\n', " ");
69 let data = data.replace('\r', " ");
70 let final_data = String::from_utf8_lossy(&strip(data)).to_string();
71 let data_uw = final_data.width();
72 (final_data, data_uw)
73}
74
75#[cfg(test)]
76pub(crate) trait Mock {
77 fn mock() -> Self;
78}
79
80#[cfg(test)]
81pub(crate) mod test {
82 use std::time::Instant;
83
84 use bytes::Bytes;
85 use tracing::Level;
86 use tracing_subscriber_init::TracingConfig;
87 use unicode_width::UnicodeWidthStr as _;
88
89 use crate::TracingConfigExt;
90
91 use super::{clean_output_string, parse_ts_ping, send_ts_ping, to_path_buf};
92
93 pub(crate) struct TestConfig {
94 verbose: u8,
95 quiet: u8,
96 level: Level,
97 directives: Option<String>,
98 }
99
100 impl TestConfig {
101 pub(crate) fn with_directives() -> Self {
102 Self {
103 verbose: 3,
104 quiet: 0,
105 level: Level::INFO,
106 directives: Some("actix_web=error".to_string()),
107 }
108 }
109 }
110
111 impl Default for TestConfig {
112 fn default() -> Self {
113 Self {
114 verbose: 3,
115 quiet: 0,
116 level: Level::INFO,
117 directives: None,
118 }
119 }
120 }
121
122 impl TracingConfig for TestConfig {
123 fn quiet(&self) -> u8 {
124 self.quiet
125 }
126
127 fn verbose(&self) -> u8 {
128 self.verbose
129 }
130 }
131
132 impl TracingConfigExt for TestConfig {
133 fn level(&self) -> Level {
134 self.level
135 }
136
137 fn enable_stdout(&self) -> bool {
138 false
139 }
140
141 fn directives(&self) -> Option<&String> {
142 self.directives.as_ref()
143 }
144 }
145
146 #[test]
147 fn test_to_path_buf() {
148 let path_str = String::from("/some/test/path");
149 let path_buf = to_path_buf(&path_str).unwrap();
150 assert_eq!(path_buf.to_str().unwrap(), "/some/test/path");
151 }
152
153 #[test]
154 fn test_clean_output_string() {
155 let input = "Hello,\tWorld!\nThis is a test.\r\x1b[31mRed Text\x1b[0m";
156 let (cleaned, width) = clean_output_string(input);
157 assert_eq!(cleaned, "Hello, World! This is a test. Red Text");
158 assert_eq!(width, cleaned.width()); }
160
161 #[test]
162 fn test_send_parse_ts_ping() {
163 let origin = Instant::now();
164 let ping = send_ts_ping(origin);
165 let bytes = Bytes::from(ping.to_vec());
166 let duration = parse_ts_ping(&bytes);
167 assert!(duration.is_some());
168 }
169
170 #[test]
171 fn test_parse_ts_ping_invalid() {
172 let bytes = Bytes::from(vec![0u8; 10]); let duration = parse_ts_ping(&bytes);
174 assert!(duration.is_none());
175 }
176}