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(¶m).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}