Repository: ngyn-rs/ngyn (language: rust)

Issue:
Support for static files
Add support to serve static files


Discussion / hints:

Fix patch:
diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs
index f791e8a..3a21ffa 100644
--- a/crates/core/src/lib.rs
+++ b/crates/core/src/lib.rs
@@ -18,7 +18,7 @@ pub mod prelude {
             Body, JsonResponse, JsonResult, NgynContext, NgynRequest, NgynResponse, Param, Query,
             ToBytes, Transducer,
         },
-        traits::{NgynGate, NgynMiddleware},
+        NgynGate, NgynMiddleware,
     };
 }
 
diff --git a/crates/shared/src/core/engine.rs b/crates/shared/src/core/engine.rs
index 759b042..db97ad9 100644
--- a/crates/shared/src/core/engine.rs
+++ b/crates/shared/src/core/engine.rs
@@ -3,16 +3,16 @@ use http::Request;
 use matchit::{Match, Router};
 use std::sync::Arc;
 
-use super::handler::RouteHandler;
+use super::handler::{handler, RouteHandler};
 use crate::{
     server::{context::AppState, Method, NgynContext, NgynResponse},
-    traits::{Middleware, NgynMiddleware},
+    Middleware, NgynMiddleware,
 };
 
 #[derive(Default)]
 pub struct PlatformData {
     router: Router<RouteHandler>,
-    middlewares: Vec<Box<dyn crate::traits::Middleware>>,
+    middlewares: Vec<Box<dyn crate::Middleware>>,
     state: Option<Arc<Box<dyn AppState>>>,
 }
 
@@ -167,6 +167,31 @@ pub trait NgynEngine: NgynPlatform {
         self.data_mut().add_middleware(Box::new(middleware));
     }
 
+    /// Sets up static file routes.
+    ///
+    /// This is great for apps tha would want to output files in a specific folder.
+    /// For instance, a `public` directory can be set up and include all files in the directory
+    fn use_static(&mut self, path_buf: std::path::PathBuf) -> std::io::Result<()> {
+        for entry in std::fs::read_dir(path_buf)? {
+            let path = entry?.path();
+
+            if path.is_file() {
+                let file_path = path.clone();
+                let file_path = file_path
+                    .to_str()
+                    .expect("file name contains invalid unicode characters");
+
+                self.get(
+                    file_path,
+                    handler(move |_| std::fs::read(path.clone()).unwrap()), // Infallible, the file will always exist
+                );
+            } else if path.is_dir() {
+                self.use_static(path)?;
+            }
+        }
+        Ok(())
+    }
+
     /// Sets the state of the application to any value that implements [`AppState`].
     ///
     /// ### Arguments
diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs
index 865d9c8..0474323 100644
--- a/crates/shared/src/lib.rs
+++ b/crates/shared/src/lib.rs
@@ -1,3 +1,95 @@
 pub mod core;
 pub mod server;
-pub mod traits;
+
+use std::{future::Future, pin::Pin};
+
+use server::context::NgynContext;
+
+/// Trait for implementing a gate.
+///
+/// Gates are how Ngyn determines if a route can activate.
+/// Sometimes, a route may need to be guarded by certain conditions.
+/// For instance, restricting access to a route based on the user's role, or checking if the user is authenticated.
+/// Typically, gates are used for this purpose.
+///
+/// ### Examples
+///
+/// ```rust
+/// # use ngyn_shared::NgynGate;
+/// # use ngyn_shared::server::NgynContext;
+///
+/// pub struct AuthGate {}
+///
+/// impl NgynGate for AuthGate {
+///    async fn can_activate(cx: &mut NgynContext<'_>) -> bool {
+///      // Check if the user is authenticated
+///      // If the user is authenticated, return true
+///      // Otherwise, return false
+///       false
+///     }
+/// }
+/// ```
+pub trait NgynGate: Send + Sync {
+    /// Determines if the gate can activate for the given request.
+    ///
+    /// ### Arguments
+    ///
+    /// * `cx` - The request context to check.
+    ///
+    /// ### Returns
+    ///
+    /// Returns `true` if the route can activate, `false` otherwise.
+    #[allow(async_fn_in_trait, unused_variables)]
+    async fn can_activate(cx: &mut NgynContext<'_>) -> bool {
+        true // default implementation
+    }
+}
+
+/// Trait for implementing a middleware.
+///
+/// Middlewares are how Ngyn processes requests.
+/// They can be used to modify the request context, the response, or both.
+///
+/// A few things to note about middlewares:
+/// - They are executed in the order they are added.
+/// - They can be used to modify the request context, the response, or both.
+/// - They can be used to short-circuit the request handling process.
+/// - They are purely synchronous and should not ideally not have side effects.
+///
+/// ### Examples
+///
+/// ```rust
+/// # use ngyn_shared::NgynMiddleware;
+/// # use ngyn_shared::server::NgynContext;
+///
+/// pub struct RequestReceivedLogger {}
+///
+/// impl NgynMiddleware for RequestReceivedLogger {
+///   async fn handle(cx: &mut NgynContext<'_>) {
+///    println!("Request received: {:?}", cx.request());
+///  }
+/// }
+/// ```
+pub trait NgynMiddleware: Send + Sync {
+    /// Handles the request.
+    #[allow(async_fn_in_trait)]
+    fn handle(cx: &mut NgynContext<'_>) -> impl std::future::Future<Output = ()> + Send
+    where
+        Self: Sized;
+}
+
+pub(crate) trait Middleware: Send + Sync {
+    fn run<'a>(
+        &'a self,
+        _cx: &'a mut NgynContext<'_>,
+    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
+}
+
+impl<'b, T: NgynMiddleware + Send + 'b> Middleware for T {
+    fn run<'a>(
+        &'a self,
+        cx: &'a mut NgynContext<'_>,
+    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
+        Box::pin(T::handle(cx))
+    }
+}
diff --git a/crates/shared/src/traits.rs b/crates/shared/src/traits.rs
deleted file mode 100644
index 4671b7f..0000000
--- a/crates/shared/src/traits.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use std::{future::Future, pin::Pin};
-
-use crate::server::NgynContext;
-
-/// Trait for implementing a gate.
-///
-/// Gates are how Ngyn determines if a route can activate.
-/// Sometimes, a route may need to be guarded by certain conditions.
-/// For instance, restricting access to a route based on the user's role, or checking if the user is authenticated.
-/// Typically, gates are used for this purpose.
-///
-/// ### Examples
-///
-/// ```rust
-/// use ngyn_shared::traits::NgynGate;
-/// use ngyn_shared::server::NgynContext;
-///
-/// pub struct AuthGate {}
-///
-/// impl NgynGate for AuthGate {
-///    async fn can_activate(cx: &mut NgynContext<'_>) -> bool {
-///      // Check if the user is authenticated
-///      // If the user is authenticated, return true
-///      // Otherwise, return false
-///       false
-///     }
-/// }
-/// ```
-pub trait NgynGate: Send + Sync {
-    /// Determines if the gate can activate for the given request.
-    ///
-    /// ### Arguments
-    ///
-    /// * `cx` - The request context to check.
-    /// * `res` - The response to optionally modify.
-    ///
-    /// ### Returns
-    ///
-    /// Returns `true` if the route can activate, `false` otherwise.
-    #[allow(async_fn_in_trait, unused_variables)]
-    async fn can_activate(cx: &mut NgynContext<'_>) -> bool {
-        true // default implementation
-    }
-}
-
-/// Trait for implementing a middleware.
-///
-/// Middlewares are how Ngyn processes requests.
-/// They can be used to modify the request context, the response, or both.
-///
-/// A few things to note about middlewares:
-/// - They are executed in the order they are added.
-/// - They can be used to modify the request context, the response, or both.
-/// - They can be used to short-circuit the request handling process.
-/// - They are purely synchronous and should not ideally not have side effects.
-///
-/// ### Examples
-///
-/// ```rust
-/// use ngyn_shared::traits::NgynMiddleware;
-/// use ngyn_shared::server::NgynContext;
-///
-/// pub struct RequestReceivedLogger {}
-///
-/// impl NgynMiddleware for RequestReceivedLogger {
-///   async fn handle(cx: &mut NgynContext<'_>) {
-///    println!("Request received: {:?}", cx.request());
-///  }
-/// }
-/// ```
-pub trait NgynMiddleware: Send + Sync {
-    /// Handles the request.
-    #[allow(async_fn_in_trait)]
-    fn handle(cx: &mut NgynContext<'_>) -> impl std::future::Future<Output = ()> + Send
-    where
-        Self: Sized;
-}
-
-pub(crate) trait Middleware: Send + Sync {
-    fn run<'a>(
-        &'a self,
-        _cx: &'a mut NgynContext<'_>,
-    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
-}
-
-impl<'b, T: NgynMiddleware + Send + 'b> Middleware for T {
-    fn run<'a>(
-        &'a self,
-        cx: &'a mut NgynContext<'_>,
-    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
-        Box::pin(T::handle(cx))
-    }
-}


Test patch: