ngyn_shared/core/
engine.rs

1use bytes::Bytes;
2use http::Request;
3use matchit::{Match, Router};
4use std::{mem::ManuallyDrop, sync::Arc};
5
6use super::handler::{handler, RouteHandler};
7use crate::{
8    server::{context::AppState, Method, NgynContext, NgynResponse, ToBytes},
9    Middleware, NgynMiddleware,
10};
11
12pub struct GroupRouter<'b> {
13    base_path: &'b str,
14    router: Router<RouteHandler>,
15}
16
17impl RouteInstance for GroupRouter<'_> {
18    fn router_mut(&mut self) -> &mut Router<RouteHandler> {
19        &mut self.router
20    }
21
22    fn mount(&self) -> &str {
23        self.base_path
24    }
25}
26
27#[derive(Default)]
28pub struct PlatformData {
29    router: Router<RouteHandler>,
30    middlewares: Vec<Box<dyn crate::Middleware>>,
31    state: Option<Arc<Box<dyn AppState>>>,
32}
33
34/// Represents platform data.
35impl PlatformData {
36    /// Process and responds to a request asynchronously.
37    ///
38    /// ### Arguments
39    ///
40    /// * `req` - The request to respond to.
41    ///
42    /// ### Returns
43    ///
44    /// The response to the request.
45    pub async fn respond(&self, req: Request<Vec<u8>>) -> NgynResponse {
46        let path = req.method().to_string() + req.uri().path();
47        let mut cx = NgynContext::from_request(req);
48
49        if let Some(state) = &self.state {
50            cx.state = Some(ManuallyDrop::new(state.into()));
51        }
52
53        let mut route_handler = None;
54        let route_info = self.router.at(&path);
55
56        if let Ok(Match { params, value, .. }) = route_info {
57            cx.params = Some(params);
58            route_handler = Some(value);
59        } else {
60            // if no route is found, we should return a 404 response
61            *cx.response_mut().status_mut() = http::StatusCode::NOT_FOUND;
62        }
63
64        // trigger global middlewares
65        for middleware in &self.middlewares {
66            middleware.run(&mut cx).await;
67        }
68
69        // run the route handler
70        if let Some(route_handler) = route_handler {
71            *cx.response_mut().body_mut() = match route_handler {
72                RouteHandler::Sync(handler) => handler(&mut cx),
73                RouteHandler::Async(async_handler) => async_handler(&mut cx).await,
74            }
75            .to_bytes()
76            .into();
77            // if the request method is HEAD, we should not return a body
78            // even if the route handler has set a body
79            if cx.request().method() == Method::HEAD {
80                *cx.response_mut().body_mut() = Bytes::default().into();
81            }
82        }
83
84        cx.response
85    }
86
87    /// Adds a middleware to the platform data.
88    ///
89    /// ### Arguments
90    ///
91    /// * `middleware` - The middleware to add.
92    pub(self) fn add_middleware(&mut self, middleware: Box<dyn Middleware>) {
93        self.middlewares.push(middleware);
94    }
95}
96
97pub trait NgynPlatform: Default {
98    fn data_mut(&mut self) -> &mut PlatformData;
99}
100
101pub trait RouteInstance {
102    fn router_mut(&mut self) -> &mut Router<RouteHandler>;
103
104    /// Mounts the route on a path, defaults to "/"
105    fn mount(&self) -> &str {
106        "/"
107    }
108
109    /// Adds a route to the platform data.
110    ///
111    /// ### Arguments
112    ///
113    /// * `path` - The path of the route.
114    /// * `method` - The HTTP method of the route.
115    /// * `handler` - The handler function for the route.
116    fn add_route(&mut self, path: &str, method: Option<Method>, handler: RouteHandler) {
117        let method = method
118            .map(|method| method.to_string())
119            .unwrap_or_else(|| "{METHOD}".to_string());
120
121        let route = if path.starts_with('/') {
122            method + path
123        } else {
124            method + self.mount() + path
125        };
126
127        self.router_mut().insert(route, handler).unwrap();
128    }
129}
130
131pub trait NgynHttpPlatform: Default {
132    fn data_mut(&mut self) -> &mut PlatformData;
133}
134
135pub trait NgynHttpEngine: NgynPlatform {
136    /// Adds a route to the application.
137    ///
138    /// ### Arguments
139    ///
140    /// * `path` - The path of the route.
141    /// * `method` - The HTTP method of the route.
142    /// * `handler` - The handler function for the route.
143    ///
144    /// ### Examples
145    ///
146    /// ```rust ignore
147    /// # use crate::{Method, NgynEngine};
148    ///
149    /// struct MyEngine;
150    ///
151    /// let mut engine = MyEngine::default();
152    /// engine.route('/', Method::GET, Box::new(|_, _| {}));
153    /// ```
154    fn route(&mut self, path: &str, method: Method, handler: impl Into<RouteHandler>) {
155        self.add_route(path, Some(method), handler.into());
156    }
157
158    /// Adds a new route to the `NgynApplication` with the `Method::Get`.
159    fn get(&mut self, path: &str, handler: impl Into<RouteHandler>) {
160        self.route(path, Method::GET, handler.into())
161    }
162
163    /// Adds a new route to the `NgynApplication` with the `Method::Post`.
164    fn post(&mut self, path: &str, handler: impl Into<RouteHandler>) {
165        self.route(path, Method::POST, handler.into())
166    }
167
168    /// Adds a new route to the `NgynApplication` with the `Method::Put`.
169    fn put(&mut self, path: &str, handler: impl Into<RouteHandler>) {
170        self.route(path, Method::PUT, handler.into())
171    }
172
173    /// Adds a new route to the `NgynApplication` with the `Method::Delete`.
174    fn delete(&mut self, path: &str, handler: impl Into<RouteHandler>) {
175        self.route(path, Method::DELETE, handler.into())
176    }
177
178    /// Adds a new route to the `NgynApplication` with the `Method::Patch`.
179    fn patch(&mut self, path: &str, handler: impl Into<RouteHandler>) {
180        self.route(path, Method::PATCH, handler.into())
181    }
182
183    /// Adds a new route to the `NgynApplication` with the `Method::Head`.
184    fn head(&mut self, path: &str, handler: impl Into<RouteHandler>) {
185        self.route(path, Method::HEAD, handler.into())
186    }
187
188    /// Sets up static file routes.
189    ///
190    /// This is great for apps tha would want to output files in a specific folder.
191    /// For instance, a `public` directory can be set up and include all files in the directory
192    ///
193    /// The behavior of `use_static` in ngyn is different from other frameworks.
194    /// 1. You can call it multiple times, each call registers a new set of routes
195    /// 2. The files in `path_buf` folder aren't embedded into your binary and must be copied to the location of your binary
196    ///
197    /// ### Arguments
198    ///
199    /// - `path_buf` - static folder, relative to Cargo.toml in dev, and the binary in release
200    ///
201    fn use_static(&mut self, path_buf: std::path::PathBuf) -> std::io::Result<()> {
202        let assets = include!("statics.rs");
203
204        for (file_path, content) in assets {
205            self.get(&file_path, handler(move |_| Bytes::from(content)));
206        }
207
208        Ok(())
209    }
210}
211
212pub trait NgynEngine: NgynPlatform {
213    fn any(&mut self, path: &str, handler: impl Into<RouteHandler>) {
214        self.add_route(path, None, handler.into());
215    }
216
217    /// Groups related routes
218    fn group(&mut self, base_path: &str, registry: impl Fn(&mut GroupRouter)) {
219        let mut group = GroupRouter {
220            base_path,
221            router: Router::<RouteHandler>::new(),
222        };
223        registry(&mut group);
224        self.data_mut().router.merge(group.router).unwrap();
225    }
226
227    /// Adds a middleware to the application.
228    ///
229    /// ### Arguments
230    ///
231    /// * `middleware` - The middleware to add.
232    fn use_middleware(&mut self, middleware: impl NgynMiddleware + 'static) {
233        self.data_mut().add_middleware(Box::new(middleware));
234    }
235
236    /// Sets the state of the application to any value that implements [`AppState`].
237    ///
238    /// ### Arguments
239    ///
240    /// * `state` - The state to set.
241    fn set_state(&mut self, state: impl AppState + 'static) {
242        self.data_mut().state = Some(Arc::new(Box::new(state)));
243    }
244}
245
246impl<T: NgynHttpPlatform> NgynPlatform for T {
247    fn data_mut(&mut self) -> &mut PlatformData {
248        self.data_mut()
249    }
250}
251
252impl<T: NgynPlatform> NgynEngine for T {}
253impl<T: NgynPlatform> RouteInstance for T {
254    fn router_mut(&mut self) -> &mut Router<RouteHandler> {
255        &mut self.data_mut().router
256    }
257}
258impl<T: NgynHttpPlatform> NgynHttpEngine for T {}
259
260#[cfg(test)]
261mod tests {
262    use http::StatusCode;
263
264    use crate::core::handler::Handler;
265    use std::any::Any;
266
267    use super::*;
268
269    struct MockAppState;
270
271    impl AppState for MockAppState {
272        fn as_any(&self) -> &dyn Any {
273            self
274        }
275
276        fn as_any_mut(&mut self) -> &mut dyn Any {
277            self
278        }
279    }
280
281    struct MockMiddleware;
282
283    impl NgynMiddleware for MockMiddleware {
284        async fn handle(cx: &mut NgynContext<'_>) {
285            *cx.response_mut().status_mut() = StatusCode::OK;
286        }
287    }
288
289    #[derive(Default)]
290    struct MockEngine {
291        data: PlatformData,
292    }
293
294    impl NgynPlatform for MockEngine {
295        fn data_mut(&mut self) -> &mut PlatformData {
296            &mut self.data
297        }
298    }
299
300    #[tokio::test]
301    async fn test_respond_with_state() {
302        let mut engine = MockEngine::default();
303        let app_state = MockAppState;
304        engine.data_mut().state = Some(Arc::new(Box::new(app_state)));
305
306        let req = Request::builder()
307            .method(Method::GET)
308            .uri("/test")
309            .body(Vec::new())
310            .unwrap();
311
312        let res = engine.data.respond(req).await;
313
314        assert_eq!(res.status(), http::StatusCode::NOT_FOUND);
315    }
316
317    #[tokio::test]
318    async fn test_respond_without_state() {
319        let engine = MockEngine::default();
320
321        let req = Request::builder()
322            .method(Method::GET)
323            .uri("/test")
324            .body(Vec::new())
325            .unwrap();
326
327        let res = engine.data.respond(req).await;
328
329        assert_eq!(res.status(), http::StatusCode::NOT_FOUND);
330    }
331
332    #[tokio::test]
333    async fn test_respond_with_middleware() {
334        let mut engine = MockEngine::default();
335        let middleware = MockMiddleware;
336        engine.data_mut().add_middleware(Box::new(middleware));
337
338        let req = Request::builder()
339            .method(Method::GET)
340            .uri("/test")
341            .body(Vec::new())
342            .unwrap();
343
344        let res = engine.data.respond(req).await;
345
346        assert_eq!(res.status(), http::StatusCode::OK);
347    }
348
349    #[tokio::test]
350    async fn test_respond_with_route_handler() {
351        let mut engine = MockEngine::default();
352        let handler: Box<Handler> = Box::new(|_| Box::new(()) as Box<dyn ToBytes>);
353        engine.add_route("/test", Some(Method::GET), RouteHandler::Sync(handler));
354
355        let req = Request::builder()
356            .method(Method::GET)
357            .uri("/test")
358            .body(Vec::new())
359            .unwrap();
360
361        let res = engine.data.respond(req).await;
362
363        assert_eq!(res.status(), http::StatusCode::OK);
364    }
365
366    #[tokio::test]
367    async fn test_respond_with_route_handler_not_found() {
368        let engine = MockEngine::default();
369
370        let req = Request::builder()
371            .method(Method::GET)
372            .uri("/test")
373            .body(Vec::new())
374            .unwrap();
375
376        let res = engine.data.respond(req).await;
377
378        assert_eq!(res.status(), http::StatusCode::NOT_FOUND);
379    }
380
381    // #[tokio::test]
382    // async fn test_respond_with_head_method() {
383    //     let mut engine = MockEngine::default();
384    //     let handler: Box<Handler> = Box::new(|_| {});
385    //     engine
386    //         .data_mut()
387    //         .add_route("/test", Some(Method::GET), RouteHandler::Sync(handler));
388
389    //     let req = Request::builder()
390    //         .method(Method::GET)
391    //         .uri("/test")
392    //         .body(Vec::new())
393    //         .unwrap();
394
395    //     let res = engine.data.respond(req).await;
396
397    //     assert_eq!(res.status(), http::StatusCode::OK);
398    // }
399
400    #[tokio::test]
401    async fn test_add_route() {
402        let mut engine = MockEngine::default();
403        let handler: Box<Handler> = Box::new(|_| Box::new(()) as Box<dyn ToBytes>);
404        engine.add_route("/test", Some(Method::GET), RouteHandler::Sync(handler));
405
406        assert!(engine.data.router.at("GET/test").is_ok());
407    }
408
409    #[tokio::test]
410    async fn test_add_middleware() {
411        let mut engine = MockEngine::default();
412        let middleware = MockMiddleware;
413        engine.data_mut().add_middleware(Box::new(middleware));
414
415        assert_eq!(engine.data.middlewares.len(), 1);
416    }
417
418    #[tokio::test]
419    async fn test_use_middleware() {
420        let mut engine = MockEngine::default();
421        let middleware = MockMiddleware;
422        engine.use_middleware(middleware);
423
424        assert_eq!(engine.data.middlewares.len(), 1);
425    }
426
427    #[tokio::test]
428    async fn test_set_state() {
429        let mut engine = MockEngine::default();
430        let app_state = MockAppState;
431        engine.set_state(app_state);
432
433        assert!(engine.data.state.is_some());
434    }
435}