rocal_core/
router.rs

1use std::{collections::HashMap, future::Future, pin::Pin};
2
3use regex::Regex;
4use url::Url;
5use wasm_bindgen::JsValue;
6use web_sys::{console, window};
7
8use crate::enums::request_method::RequestMethod;
9
10type Action = Box<dyn Fn(HashMap<String, String>) -> Pin<Box<dyn Future<Output = ()>>>>;
11
12struct Node {
13    children: HashMap<String, Node>,
14    action: Option<Action>,
15}
16
17pub struct Router {
18    root: Node,
19}
20
21impl Router {
22    const HOST: &str = "https://www.example.com";
23
24    pub fn new() -> Self {
25        Router {
26            root: Node {
27                children: HashMap::new(),
28                action: None,
29            },
30        }
31    }
32
33    pub fn register(&mut self, method: RequestMethod, route: &str, action: Action) {
34        let mut ptr = &mut self.root;
35
36        if !ptr.children.contains_key(&method.to_string()) {
37            ptr.children.insert(
38                method.to_string(),
39                Node {
40                    children: HashMap::new(),
41                    action: None,
42                },
43            );
44        }
45
46        ptr = ptr.children.get_mut(&method.to_string()).unwrap();
47
48        for s in route.split("/") {
49            if !ptr.children.contains_key(s) {
50                ptr.children.insert(
51                    s.to_string(),
52                    Node {
53                        children: HashMap::new(),
54                        action: None,
55                    },
56                );
57            }
58
59            ptr = ptr.children.get_mut(s).unwrap();
60        }
61
62        ptr.action = Some(action);
63    }
64
65    pub async fn resolve(
66        &self,
67        method: RequestMethod,
68        route: &str,
69        action_args: Option<HashMap<String, String>>,
70    ) -> bool {
71        let mut route = route.to_string();
72        let path_param_regex: Regex = Regex::new(r"^<(?<key>.+)>$").unwrap();
73
74        let mut action_args: HashMap<String, String> = action_args.unwrap_or(HashMap::new());
75
76        if let Ok(url) = Url::parse(&format!("{}{}", Self::HOST, route)) {
77            for (k, v) in url.query_pairs() {
78                action_args.insert(k.to_string(), v.to_string());
79            }
80            route = url.path().to_string();
81        }
82
83        let mut ptr = &self.root;
84
85        if !ptr.children.contains_key(&method.to_string()) {
86            return false;
87        }
88
89        ptr = ptr.children.get(&method.to_string()).unwrap();
90
91        for s in route.split("/") {
92            if !ptr.children.contains_key(s) {
93                if let Some(param) = ptr
94                    .children
95                    .keys()
96                    .find(|key| path_param_regex.is_match(key))
97                {
98                    let caps = path_param_regex.captures(&param).unwrap();
99                    action_args.insert(caps["key"].to_string(), s.to_string());
100                    ptr = ptr.children.get(param).unwrap();
101                    continue;
102                } else {
103                    return false;
104                }
105            }
106
107            ptr = ptr.children.get(s).unwrap();
108        }
109
110        if let Some(action) = &ptr.action {
111            action(action_args).await;
112            true
113        } else {
114            false
115        }
116    }
117
118    pub async fn redirect(&self, path: &str) -> bool {
119        let result = self.resolve(RequestMethod::Get, path, None).await;
120
121        if !result {
122            return false;
123        }
124
125        let win = if let Some(win) = window() {
126            win
127        } else {
128            return false;
129        };
130
131        let (history, origin) =
132            if let (Ok(history), Ok(origin)) = (win.history(), win.location().origin()) {
133                (history, origin)
134            } else {
135                return false;
136            };
137
138        let abs = format!("{}/#{}", origin, path);
139
140        if let Err(err) = history.push_state_with_url(&JsValue::NULL, "", Some(&abs)) {
141            console::error_1(&err);
142            return false;
143        }
144
145        true
146    }
147}
148
149pub fn link_to(path: &str, remote: bool) -> String {
150    if remote {
151        format!("{}", path)
152    } else {
153        format!("/#{}", path)
154    }
155}