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