Skip to main content

ntex_error/
utils.rs

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