aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2022-04-02 00:48:47 +0000
committerFuwn <[email protected]>2022-04-02 00:48:47 +0000
commit8869b6ef044aa42043f8e17ee0d56767730d56fc (patch)
tree465d58df7fd5ec125f7ebe9400e6b6d28ed8c7ff
parentbuild(cargo): simplify example usage (diff)
downloadwindmark-8869b6ef044aa42043f8e17ee0d56767730d56fc.tar.xz
windmark-8869b6ef044aa42043f8e17ee0d56767730d56fc.zip
feat(router): stateful modules!
-rw-r--r--examples/windmark.rs33
-rw-r--r--src/lib.rs115
-rw-r--r--src/module.rs30
-rw-r--r--src/returnable.rs19
4 files changed, 189 insertions, 8 deletions
diff --git a/examples/windmark.rs b/examples/windmark.rs
index 211d235..dc69bbe 100644
--- a/examples/windmark.rs
+++ b/examples/windmark.rs
@@ -21,7 +21,35 @@
#[macro_use]
extern crate log;
-use windmark::Response;
+use windmark::{returnable::CallbackContext, Response, Router};
+
+#[derive(Default)]
+struct Clicker {
+ clicks: isize,
+}
+impl windmark::Module for Clicker {
+ fn on_attach(&mut self, _: &mut Router) {
+ println!("clicker has been attached!");
+ }
+
+ fn on_pre_route(&mut self, context: CallbackContext<'_>) {
+ self.clicks += 1;
+
+ info!(
+ "clicker has been called pre-route on {} with {} clicks!",
+ context.url.path(),
+ self.clicks
+ );
+ }
+
+ fn on_post_route(&mut self, context: CallbackContext<'_>) {
+ info!(
+ "clicker has been called post-route on {} with {} clicks!",
+ context.url.path(),
+ self.clicks
+ );
+ }
+}
#[windmark::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -38,12 +66,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Response::PermanentFailure("e".into())
}))
- .attach(|r| {
+ .attach_stateless(|r| {
r.mount(
"/module",
Box::new(|_| Response::Success("This is a module!".into())),
);
})
+ .attach(Clicker::default())
.set_pre_route_callback(Box::new(|stream, url, _| {
info!(
"accepted connection from {} to {}",
diff --git a/src/lib.rs b/src/lib.rs
index ce6592d..7e49f97 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -98,8 +98,9 @@
#![recursion_limit = "128"]
mod handler;
+pub mod module;
pub mod response;
-pub(crate) mod returnable;
+pub mod returnable;
pub mod utilities;
#[macro_use]
@@ -110,6 +111,7 @@ use std::{
sync::{Arc, Mutex},
};
+pub use module::Module;
use openssl::ssl::{self, SslAcceptor, SslMethod};
pub use response::Response;
pub use tokio::main;
@@ -122,7 +124,7 @@ use url::Url;
use crate::{
handler::{Callback, ErrorResponse, Partial, RouteResponse},
response::to_value_set_status,
- returnable::{ErrorContext, RouteContext},
+ returnable::{CallbackContext, ErrorContext, RouteContext},
};
/// A router which takes care of all tasks a Windmark server should handle:
@@ -143,6 +145,7 @@ pub struct Router {
charset: String,
language: String,
port: i32,
+ modules: Arc<Mutex<Vec<Box<dyn Module + Send>>>>,
}
impl Router {
/// Create a new `Router`
@@ -325,6 +328,7 @@ impl Router {
let mut buffer = [0u8; 1024];
let mut url = Url::parse("gemini://fuwn.me/")?;
let mut response_status = 0;
+ #[cfg(not(feature = "auto-deduce-mime"))]
let mut response_mime_type = "".to_string();
let mut footer = String::new();
let mut header = String::new();
@@ -340,7 +344,17 @@ impl Router {
}
}
- let route = self.routes.at(url.path());
+ let route = &mut self.routes.at(url.path());
+
+ for module in &mut *self.modules.lock().unwrap() {
+ module.on_pre_route(CallbackContext::new(stream.get_ref(), &url, {
+ if let Ok(route) = &route {
+ Some(&route.params)
+ } else {
+ None
+ }
+ }));
+ }
(*self.pre_route_callback).lock().unwrap().call_mut((
stream.get_ref(),
@@ -432,6 +446,16 @@ impl Router {
)
.await?;
+ for module in &mut *self.modules.lock().unwrap() {
+ module.on_post_route(CallbackContext::new(stream.get_ref(), &url, {
+ if let Ok(route) = &route {
+ Some(&route.params)
+ } else {
+ None
+ }
+ }));
+ }
+
(*self.post_route_callback).lock().unwrap().call_mut((
stream.get_ref(),
&url,
@@ -590,17 +614,19 @@ impl Router {
self
}
- /// Attach a module to a `Router`.
+ /// Attach a stateless module to a `Router`.
///
/// A module is an extension or middleware to a `Router`. Modules get full
/// access to the `Router`, but can be extended by a third party.
///
/// # Examples
///
+ /// ## Integrated Module
+ ///
/// ```rust
/// use windmark::Response;
///
- /// windmark::Router::new().attach(|r| {
+ /// windmark::Router::new().attach_stateless(|r| {
/// r.mount(
/// "/module",
/// Box::new(|_| Response::Success("This is a module!".into())),
@@ -612,13 +638,89 @@ impl Router {
/// }));
/// });
/// ```
- pub fn attach<F>(&mut self, mut module: F) -> &mut Self
+ ///
+ /// ## External Module
+ ///
+ /// ```rust
+ /// use windmark::Response;
+ ///
+ /// mod windmark_example {
+ /// pub fn module(router: &mut windmark::Router) {
+ /// router.mount(
+ /// "/module",
+ /// Box::new(|_| windmark::Response::Success("This is a module!".into())),
+ /// );
+ /// }
+ /// }
+ ///
+ /// windmark::Router::new().attach_stateless(windmark_example::module);
+ /// ```
+ pub fn attach_stateless<F>(&mut self, mut module: F) -> &mut Self
where F: FnMut(&mut Self) {
module(self);
self
}
+ /// Attach a stateful module to a `Router`.
+ ///
+ /// Like a stateless module is an extension or middleware to a `Router`.
+ /// Modules get full access to the `Router` and can be extended by a third
+ /// party, but also, can create hooks will be executed through various parts
+ /// of a routes' lifecycle. Stateful modules also have state, so variables can
+ /// be stored for further access.
+ ///
+ /// # Panics
+ ///
+ /// May panic if the stateful module cannot be attached.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use log::info;
+ /// use windmark::{returnable::CallbackContext, Response, Router};
+ ///
+ /// #[derive(Default)]
+ /// struct Clicker {
+ /// clicks: isize,
+ /// }
+ /// impl windmark::Module for Clicker {
+ /// fn on_attach(&mut self, _: &mut Router) {
+ /// info!("clicker has been attached!");
+ /// }
+ ///
+ /// fn on_pre_route(&mut self, context: CallbackContext<'_>) {
+ /// self.clicks += 1;
+ ///
+ /// info!(
+ /// "clicker has been called pre-route on {} with {} clicks!",
+ /// context.url.path(),
+ /// self.clicks
+ /// );
+ /// }
+ ///
+ /// fn on_post_route(&mut self, context: CallbackContext<'_>) {
+ /// info!(
+ /// "clicker has been called post-route on {} with {} clicks!",
+ /// context.url.path(),
+ /// self.clicks
+ /// );
+ /// }
+ /// }
+ ///
+ /// Router::new().attach(Clicker::default());
+ /// ```
+ pub fn attach(
+ &mut self,
+ mut module: impl Module + 'static + Send,
+ ) -> &mut Self {
+ module.on_attach(self);
+
+ (*self.modules.lock().unwrap()).push(Box::new(module));
+
+ self
+ }
+
/// Specify a custom character set.
///
/// Defaults to `"utf-8"`.
@@ -689,6 +791,7 @@ impl Default for Router {
charset: "utf-8".to_string(),
language: "en".to_string(),
port: 1965,
+ modules: Arc::new(Mutex::new(vec![])),
}
}
}
diff --git a/src/module.rs b/src/module.rs
new file mode 100644
index 0000000..0d2d2f5
--- /dev/null
+++ b/src/module.rs
@@ -0,0 +1,30 @@
+// This file is part of Windmark <https://github.com/gemrest/windmark>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::{returnable::CallbackContext, Router};
+
+pub trait Module {
+ /// Called right after the module is attached.
+ fn on_attach(&mut self, _: &mut Router) {}
+
+ /// Called before a route is mounted.
+ fn on_pre_route(&mut self, _: CallbackContext<'_>) {}
+
+ /// Called after a route is mounted.
+ fn on_post_route(&mut self, _: CallbackContext<'_>) {}
+}
diff --git a/src/returnable.rs b/src/returnable.rs
index 8f4ee81..31d7e4f 100644
--- a/src/returnable.rs
+++ b/src/returnable.rs
@@ -51,3 +51,22 @@ impl<'a> ErrorContext<'a> {
}
}
}
+
+pub struct CallbackContext<'a> {
+ pub tcp: &'a TcpStream,
+ pub url: &'a Url,
+ pub params: Option<&'a Params<'a, 'a>>,
+}
+impl<'a> CallbackContext<'a> {
+ pub const fn new(
+ tcp: &'a TcpStream,
+ url: &'a Url,
+ params: Option<&'a Params<'a, 'a>>,
+ ) -> Self {
+ Self {
+ tcp,
+ url,
+ params,
+ }
+ }
+}