pillow_routing/
router.rs

1use std::{collections::HashMap, sync::Arc};
2
3use crate::route::Route;
4
5#[allow(unused_imports)]
6use pillow_http::{handler::Handler, middlewares::Middleware, Request, Response};
7
8/// The Main router in your app
9///
10/// ```rust
11/// #[tokio::main]
12/// async fn main(){
13///     let mut router = MainRouter::new();
14/// }
15/// ```
16#[allow(dead_code)]
17pub struct MainRouter {
18    routes: HashMap<pillow_http::http_methods::HttpMethods, Vec<Route>>,
19}
20
21impl MainRouter {
22    /// Instance of a router
23    pub fn new() -> Self {
24        Self {
25            routes: HashMap::new(),
26        }
27    }
28
29    /// Reference of routes
30    pub fn routes(&self) -> &HashMap<pillow_http::http_methods::HttpMethods, Vec<Route>> {
31        &self.routes
32    }
33
34    fn get_routes_from_method(
35        &self,
36        method: &pillow_http::http_methods::HttpMethods,
37    ) -> Option<&Vec<Route>> {
38        self.routes.get(&method)
39    }
40
41    fn get_option_index(&self, uri: &pillow_http::Uri, routes_vec: &Vec<Route>) -> Option<usize> {
42        routes_vec.iter().position(|route| route.uri() == uri)
43    }
44
45    fn response_404(&self, response: &mut Response, request: &Request) -> Response {
46        response.set_status_code(pillow_http::status_code::StatusCode::ClientError(
47            pillow_http::status_code::ClientError::NotFound,
48        ));
49
50        response.add_header(
51            pillow_http::header::Header::ContentType,
52            "text/html".to_string(),
53        );
54
55        let content = format!(
56            "<html>
57                <head>
58                    <style>
59                        * {{
60                            margin: 0;
61                            padding: 0;
62                        }}
63                        body {{
64                            height: 100vh;
65                            display: flex;
66                            justify-content: center;
67                            align-items: center;
68                            background-color: #080808;
69                        }}
70                        h1 {{
71                            font-family: system-ui;
72                            color: #fefefe;
73                        }}
74                    </style>
75                </head>
76                <body>
77                    <h1>Not Found {} {}</h1>
78                </body>
79            </html>",
80            &request.method().as_str(),
81            &request.uri().0.as_str()
82        );
83
84        response.insert_string_content(content);
85
86        response.clone()
87    }
88
89    fn use_controller(&self, route: &Route, request: &Request) -> Response {
90        let response = route.use_controller(&request);
91
92        response.clone()
93    }
94
95    fn search_route_with_params(
96        &self,
97        request: &mut Request,
98        response: &mut Response,
99        routes_with_params: Vec<&Route>,
100    ) -> Response {
101        let mut response = response.clone();
102
103        for route in routes_with_params {
104            let path: Vec<_> = route
105                .regex_complete
106                .split(&route.uri().0.as_str())
107                .collect();
108
109            let path_param: Vec<_> = route
110                .regex_words
111                .find_iter(&request.uri().0.as_str())
112                .collect();
113
114            if request.uri().0.starts_with(path[0]) {
115                let key = &route.params()[0].clone();
116                let value = path_param[1].as_str().to_string();
117
118                request.add_params((key.clone(), value));
119
120                response = self.use_controller(route, &request);
121            } else {
122                return self.response_404(&mut response, &request);
123            }
124        }
125
126        response.clone()
127    }
128
129    pub(crate) fn routing(&self, request_ref: &Request) -> Vec<Response> {
130        // Clone the request
131        let mut request = request_ref.clone();
132
133        // Create the response to send a client
134        let mut response = Response::new_empty();
135
136        let option_routes_vec = self.get_routes_from_method(request.method());
137        let routes_vec: &Vec<Route>;
138
139        match option_routes_vec {
140            Some(routes) => routes_vec = routes,
141            // Return 404
142            None => return vec![self.response_404(&mut response, &request)],
143        }
144
145        let option_index = self.get_option_index(request.uri(), &routes_vec);
146
147        match option_index {
148            // IF find route index
149            Some(index) => {
150                response = self.use_controller(&routes_vec[index], &request);
151            }
152
153            //IF not find route index
154            None => {
155                //Find if there is any routes with params
156                let routes_params: Vec<_> = routes_vec
157                    .iter()
158                    .filter(|route| route.has_parameters())
159                    .collect();
160
161                // IF not have params
162                if routes_params.len() == 0 {
163                    return vec![self.response_404(&mut response, &request)];
164                }
165
166                // Search route have the url
167                response =
168                    self.search_route_with_params(&mut request, &mut response, routes_params);
169            }
170        }
171
172        vec![response]
173    }
174}
175
176// impl<T: pillow_http::handler::Handler + Send + Sync + std::fmt::Debug> MainRouter<T> {
177impl MainRouter {
178    pub fn add_route_closure<T>(
179        &mut self,
180        method: pillow_http::http_methods::HttpMethods,
181        path: &str,
182        controller: T,
183    ) where
184        T: Fn(&Request) -> Response + Send + Sync + 'static,
185    {
186        let uri = path.to_string();
187
188        self.routes
189            .entry(method)
190            .or_insert(Vec::new())
191            .push(Route::new(uri, method, controller));
192    }
193
194    /// Add Route
195    ///
196    /// # Arguments
197    ///
198    /// * `uri` - Path of route
199    /// * `controller` - Callback function
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use pillow::http::*;
205    ///
206    /// #[controller(method = "GET", path = "/")]
207    /// fn index (_: Request) -> Response {
208    ///     Response::text("hello")
209    /// }
210    ///
211    /// #[tokio::main]
212    /// async fn main (){
213    ///     let mut router = MainRouter::new();
214    ///
215    ///     router.add_route(route!(index {}));
216    /// }
217    /// ```
218    pub fn add_route(&mut self, route: Route) {
219        self.routes
220            .entry(route.method().clone())
221            .or_insert(Vec::new())
222            .push(route)
223    }
224
225    /// Add files from the public directory
226    ///
227    /// ```rust
228    /// use pillow::http::*;
229    ///
230    /// #[tokio::main]
231    /// async fn main (){
232    ///     let mut router = MainRouter::new();
233    ///     router.public();
234    /// }
235    ///
236    /// ```
237    pub fn public(&mut self) {
238        self.static_files("public");
239    }
240
241    /// Add assets from the resources directory
242    ///
243    /// ```rust
244    /// use pillow::http::*;
245    ///
246    /// #[tokio::main]
247    /// async fn main (){
248    ///     let mut router = MainRouter::new();
249    ///     router.public();
250    /// }
251    ///
252    /// ```
253    pub fn assets(&mut self) {
254        self.static_files("resources/js");
255        self.static_files("resources/css");
256    }
257
258    /// Insert static files in the routes
259    ///
260    /// ```rust
261    /// fn (&mut self) {
262    ///     self.static_files("static");
263    /// }
264    /// ```
265    fn static_files(&mut self, path: &str) {
266        let static_files = pillow_http::static_files::StaticFiles::new(path);
267
268        for file in static_files.files {
269            let path = file.path.clone();
270            let content_type = Arc::new(file.content_type().unwrap().to_owned());
271
272            let decoded_bytes = file.clone().content;
273
274            let closure = move |_: &Request| -> Response {
275                let content_type =
276                    pillow_http::header::from_str_ext_to_content_type(&content_type.clone());
277
278                let content = decoded_bytes.clone();
279
280                Response::file(content_type, content.clone())
281            };
282
283            self.add_route_closure(pillow_http::http_methods::HttpMethods::GET, &path, closure);
284        }
285    }
286}