Skip to main content

ruest/router/
path.rs

1/// Concatène deux segments de chemin HTTP en `&'static str` (compile-time friendly).
2///
3/// Les macros passent des littéraux ; le résultat est alloué une fois au démarrage
4/// si nécessaire, mais **pas** par requête.
5#[inline]
6pub fn join_paths(prefix: &str, suffix: &str) -> &'static str {
7    let prefix = prefix.trim_end_matches('/');
8    let suffix = if suffix.starts_with('/') {
9        suffix
10    } else {
11        // suffix non-literal possible : allocation unique au bootstrap
12        let owned = format!("/{suffix}");
13        return Box::leak(owned.into_boxed_str());
14    };
15
16    if suffix == "/" || suffix.is_empty() {
17        let path = if prefix.is_empty() { "/" } else { prefix };
18        return Box::leak(format!("{path}/").into_boxed_str());
19    }
20
21    let combined = if prefix.is_empty() {
22        suffix.to_string()
23    } else {
24        format!("{prefix}{suffix}")
25    };
26    Box::leak(combined.into_boxed_str())
27}
28
29/// Variante sans allocation quand prefix et suffix sont des littéraux connus à la macro.
30#[macro_export]
31macro_rules! static_path {
32    ($prefix:literal, $suffix:literal) => {{
33        const PATH: &str = concat!($prefix, $suffix);
34        PATH
35    }};
36}
37
38#[cfg(test)]
39mod tests {
40    use super::join_paths;
41
42    #[test]
43    fn joins_prefix_and_suffix() {
44        let path = join_paths("/api", "/users");
45        assert_eq!(path, "/api/users");
46    }
47
48    #[test]
49    fn trims_trailing_slash_on_prefix_when_suffix_is_absolute() {
50        let path = join_paths("/api/", "/health");
51        assert_eq!(path, "/api/health");
52    }
53
54    #[test]
55    fn root_suffix_on_prefix() {
56        let path = join_paths("/ping", "/");
57        assert_eq!(path, "/ping/");
58    }
59
60    #[test]
61    fn empty_prefix_uses_suffix() {
62        let path = join_paths("", "/only");
63        assert_eq!(path, "/only");
64    }
65}