diff options
| author | Fuwn <[email protected]> | 2022-04-02 00:48:47 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2022-04-02 00:48:47 +0000 |
| commit | 8869b6ef044aa42043f8e17ee0d56767730d56fc (patch) | |
| tree | 465d58df7fd5ec125f7ebe9400e6b6d28ed8c7ff | |
| parent | build(cargo): simplify example usage (diff) | |
| download | windmark-8869b6ef044aa42043f8e17ee0d56767730d56fc.tar.xz windmark-8869b6ef044aa42043f8e17ee0d56767730d56fc.zip | |
feat(router): stateful modules!
| -rw-r--r-- | examples/windmark.rs | 33 | ||||
| -rw-r--r-- | src/lib.rs | 115 | ||||
| -rw-r--r-- | src/module.rs | 30 | ||||
| -rw-r--r-- | src/returnable.rs | 19 |
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 {}", @@ -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, + } + } +} |