1use std::{
2 borrow::Cow, cell::RefCell, convert::Infallible, error::Error as StdError, fmt, io, path::Path,
3 path::PathBuf,
4};
5
6use ntex_bytes::ByteString;
7
8use crate::{Error, ErrorDiagnostic, ResultType};
9
10pub trait Retryable {
12 fn is_retryable(&self) -> bool;
13}
14
15impl<T, E> Retryable for Result<T, E>
16where
17 E: Retryable,
18{
19 fn is_retryable(&self) -> bool {
20 match self {
21 Ok(_) => false,
22 Err(err) => err.is_retryable(),
23 }
24 }
25}
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
29pub struct ResultSignature(pub &'static str);
30
31impl ResultSignature {
32 pub fn new(sig: &'static str) -> Self {
34 Self(sig)
35 }
36
37 pub fn signature(self) -> &'static str {
39 self.0
40 }
41}
42
43impl<'a, E: ErrorDiagnostic> From<&'a E> for ResultSignature {
44 fn from(err: &'a E) -> Self {
45 ResultSignature::new(err.signature())
46 }
47}
48
49impl<'a, T, E: ErrorDiagnostic> From<&'a Result<T, E>> for ResultSignature {
50 fn from(result: &'a Result<T, E>) -> Self {
51 match result {
52 Ok(_) => ResultSignature(ResultType::Success.as_str()),
53 Err(err) => ResultSignature(err.signature()),
54 }
55 }
56}
57
58impl ErrorDiagnostic for Infallible {
59 fn signature(&self) -> &'static str {
60 unreachable!()
61 }
62}
63
64impl ErrorDiagnostic for io::Error {
65 fn typ(&self) -> ResultType {
66 match self.kind() {
67 io::ErrorKind::InvalidData
68 | io::ErrorKind::InvalidInput
69 | io::ErrorKind::Unsupported
70 | io::ErrorKind::UnexpectedEof
71 | io::ErrorKind::BrokenPipe
72 | io::ErrorKind::ConnectionReset
73 | io::ErrorKind::ConnectionAborted
74 | io::ErrorKind::NotConnected
75 | io::ErrorKind::TimedOut => ResultType::ClientError,
76 _ => ResultType::ServiceError,
77 }
78 }
79
80 fn signature(&self) -> &'static str {
81 match self.kind() {
82 io::ErrorKind::InvalidData => "io-InvalidData",
83 io::ErrorKind::InvalidInput => "io-InvalidInput",
84 io::ErrorKind::Unsupported => "io-Unsupported",
85 io::ErrorKind::UnexpectedEof => "io-UnexpectedEof",
86 io::ErrorKind::BrokenPipe => "io-BrokenPipe",
87 io::ErrorKind::ConnectionReset => "io-ConnectionReset",
88 io::ErrorKind::ConnectionAborted => "io-ConnectionAborted",
89 io::ErrorKind::NotConnected => "io-NotConnected",
90 io::ErrorKind::TimedOut => "io-TimedOut",
91 _ => "io-Error",
92 }
93 }
94}
95
96#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
97pub struct Success;
98
99impl StdError for Success {}
100
101impl ErrorDiagnostic for Success {
102 fn typ(&self) -> ResultType {
103 ResultType::Success
104 }
105
106 fn signature(&self) -> &'static str {
107 ResultType::Success.as_str()
108 }
109}
110
111impl fmt::Display for Success {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
113 write!(f, "Success")
114 }
115}
116
117pub async fn with_service<F, T, E>(svc: &'static str, fut: F) -> F::Output
121where
122 F: Future<Output = Result<T, Error<E>>>,
123 E: ErrorDiagnostic + Clone,
124{
125 fut.await.map_err(|err: Error<E>| {
126 if err.service().is_none() {
127 err.set_service(svc)
128 } else {
129 err
130 }
131 })
132}
133
134pub fn module_path(file_path: &str) -> ByteString {
136 module_path_ext("", "", "::", "", file_path)
137}
138
139pub fn module_path_prefix(prefix: &'static str, file_path: &str) -> ByteString {
141 module_path_ext(prefix, "", "::", "", file_path)
142}
143
144pub fn module_path_fs(file_path: &str) -> ByteString {
146 module_path_ext("", "/src", "/", ".rs", file_path)
147}
148
149fn module_path_ext(
150 prefix: &'static str,
151 mod_sep: &str,
152 sep: &str,
153 suffix: &str,
154 file_path: &str,
155) -> ByteString {
156 type HashMap<K, V> = std::collections::HashMap<K, V, foldhash::fast::RandomState>;
157 thread_local! {
158 static CACHE: RefCell<HashMap<&'static str, HashMap<String, ByteString>>> = RefCell::new(HashMap::default());
159 }
160
161 let cached = CACHE.with(|cache| {
162 if let Some(c) = cache.borrow().get(prefix) {
163 c.get(file_path).cloned()
164 } else {
165 None
166 }
167 });
168
169 if let Some(cached) = cached {
170 cached
171 } else {
172 let normalized_file_path = normalize_file_path(file_path);
173 let (module_name, module_root) = module_root_from_file(mod_sep, &normalized_file_path);
174 let module = module_path_from_file_with_root(
175 prefix,
176 sep,
177 &normalized_file_path,
178 &module_name,
179 &module_root,
180 suffix,
181 );
182
183 let _ = CACHE.with(|cache| {
184 cache
185 .borrow_mut()
186 .entry(prefix)
187 .or_default()
188 .insert(file_path.to_string(), module.clone())
189 });
190 module
191 }
192}
193
194fn normalize_file_path(file_path: &str) -> String {
195 let path = Path::new(file_path);
196 if path.is_absolute() {
197 return path.to_string_lossy().into_owned();
198 }
199
200 match std::env::current_dir() {
201 Ok(cwd) => cwd.join(path).to_string_lossy().into_owned(),
202 Err(_) => file_path.to_string(),
203 }
204}
205
206fn module_root_from_file(mod_sep: &str, file_path: &str) -> (String, PathBuf) {
207 let normalized = file_path.replace('\\', "/");
208 if let Some((root, _)) = normalized.rsplit_once("/src/") {
209 let mut root = PathBuf::from(root);
210 let mod_name = root
211 .file_name()
212 .map_or(Cow::Borrowed("crate"), |s| s.to_string_lossy());
213 let mod_name = if mod_sep.is_empty() {
214 mod_name.replace('-', "_")
215 } else {
216 mod_name.to_string()
217 };
218 root.push("src");
219 return (format!("{mod_name}{mod_sep}"), root);
220 }
221
222 let path = Path::new(file_path)
223 .parent()
224 .map_or_else(|| PathBuf::from("."), Path::to_path_buf);
225
226 let m = path
227 .parent()
228 .and_then(|p| p.file_name())
229 .map_or_else(|| Cow::Borrowed("crate"), |p| p.to_string_lossy());
230
231 (format!("{m}{mod_sep}"), path)
232}
233
234fn module_path_from_file(sep: &str, file_path: &str) -> String {
235 let normalized = file_path.replace('\\', "/");
236 let relative = normalized
237 .split_once("/src/")
238 .map_or(normalized.as_str(), |(_, tail)| tail);
239
240 if relative == "lib.rs" || relative == "main.rs" {
241 return relative.to_string();
242 }
243
244 let without_ext = relative.strip_suffix(".rs").unwrap_or(relative);
245 if without_ext.ends_with("/mod") {
246 let parent = without_ext.strip_suffix("/mod").unwrap_or(without_ext);
247 let parent = parent.trim_matches('/');
248 return parent.replace('/', sep);
249 }
250
251 let module = without_ext.trim_matches('/').replace('/', sep);
252 if module.is_empty() {
253 "crate".to_string()
254 } else {
255 module
256 }
257}
258
259fn module_path_from_file_with_root(
260 prefix: &str,
261 sep: &str,
262 file_path: &str,
263 module_name: &str,
264 module_root: &Path,
265 suffix: &str,
266) -> ByteString {
267 let normalized = file_path.replace('\\', "/");
268 let module_root_norm = module_root.to_string_lossy().replace('\\', "/");
269
270 let Some(relative) = normalized.strip_prefix(&(module_root_norm.clone() + "/")) else {
271 return format!(
272 "{prefix}{module_name}{sep}{}{suffix}",
273 module_path_from_file(sep, file_path)
274 )
275 .into();
276 };
277 if relative == "lib.rs" || relative == "main.rs" {
278 return ByteString::from(format!("{prefix}{module_name}{sep}{relative}"));
279 }
280
281 let without_ext = relative.strip_suffix(".rs").unwrap_or(relative);
282 if without_ext.ends_with("/mod") {
283 let parent = without_ext.strip_suffix("/mod").unwrap_or(without_ext);
284 let parent = parent.trim_matches('/');
285 return format!(
286 "{prefix}{module_name}{sep}{}{sep}mod{suffix}",
287 parent.replace('/', sep)
288 )
289 .into();
290 }
291
292 let module = without_ext.trim_matches('/').replace('/', sep);
293 if module.is_empty() {
294 ByteString::from(format!("{prefix}{module_name}{suffix}"))
295 } else {
296 format!("{prefix}{module_name}{sep}{module}{suffix}").into()
297 }
298}