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