#![allow(unused)]
use std::{collections::HashMap, future::Future, sync::Arc, str::FromStr, marker::PhantomData, any::{Any, TypeId}, marker::Copy, pin::Pin};
use http::{StatusCode, Request, response, Response, Uri};
use lazy_regex::Regex;
use crate::util::{uri_decode, uri_encode};
#[derive(Clone)]
pub struct Droughter {
root: Option<Arc<dyn Drought>>,
routes: Vec<Vec<Arc<dyn Drought>>>,
req_irrigators: Vec<Arc<dyn RequestIrrigator>>,
res_irrigators: Vec<Arc<dyn ResponseIrrigator>>
}
#[derive(Debug)]
pub enum DroughtResult {
Handle(Response<Vec<u8>>),
NotFound,
Ignore,
Drop,
DropWith(Response<Vec<u8>>)
}
#[derive(Debug)]
pub enum IrrigatorResult {
ReplyNow(DroughtResult),
ContinueWith(Vec<String>, Request<Vec<u8>>),
ContinueWithPath(Vec<String>),
ContinueWithReq(Request<Vec<u8>>),
Continue
}
#[derive(Debug)]
pub enum DroughterResult {
Handle(Response<Vec<u8>>),
Ignore,
Drop,
DropWith(Response<Vec<u8>>)
}
pub trait Drought {
fn handle<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, map: Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>>;
}
pub trait RequestIrrigator {
fn map<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, map: Matches) -> Pin<Box<dyn Future<Output = IrrigatorResult> + 'a>>;
}
pub trait ResponseIrrigator {
fn map<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, res: &'a mut DroughtResult, map: &'a Matches) -> Pin<Box<dyn Future<Output = ()> + 'a>>;
}
#[derive(Default)]
pub struct DroughterBuilder {
routes: HashMap<usize, Vec<Box<dyn Drought>>>,
req_irrigators: Vec<Box<dyn RequestIrrigator>>,
res_irrigators: Vec<Box<dyn ResponseIrrigator>>
}
impl DroughterBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn insert_drought(&mut self, priority: usize, drought: impl Drought + 'static) {
if !self.routes.contains_key(&priority) { self.routes.insert(priority, Vec::new()); }
self.routes.get_mut(&priority).unwrap().push(Box::new(drought));
}
pub fn with_drought(mut self, priority: usize, drought: impl Drought + 'static) -> Self {
self.insert_drought(priority, drought);
self
}
pub fn insert_sub(&mut self, priority: usize, path: &str, droughter: Droughter) {
self.insert_drought(priority, SubDrought{ path: path.to_string(), droughter });
}
pub fn with_sub(mut self, priority: usize, path: &str, droughter: Droughter) -> Self {
self.insert_sub(priority, path, droughter);
self
}
pub fn insert_typed<T: FromStr + 'static>(&mut self, priority: usize, name: &str, droughter: Droughter) {
self.insert_drought(priority, TypedDrought::<T>{ name: name.to_string(), droughter, _pd: PhantomData::default() });
}
pub fn with_typed<T: FromStr + 'static>(mut self, priority: usize, name: &str, droughter: Droughter) -> Self {
self.insert_typed::<T>(priority, name, droughter);
self
}
pub fn insert_matched(&mut self, priority: usize, name: &str, regex: Regex, droughter: Droughter) {
self.insert_drought(priority, MatchedDrought{ name: name.to_string(), regex, droughter });
}
pub fn with_matched(mut self, priority: usize, name: &str, regex: Regex, droughter: Droughter) -> Self {
self.insert_matched(priority, name, regex, droughter);
self
}
pub fn insert_lambda(&mut self, priority: usize, func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>>) {
self.insert_drought(priority, LambdaDrought{ func });
}
pub fn with_lambda(mut self, priority: usize, func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>>) -> Self {
self.insert_lambda(priority, func);
self
}
pub fn insert_request_irrigator(&mut self, irrigator: impl RequestIrrigator + 'static) {
self.req_irrigators.push(Box::new(irrigator))
}
pub fn with_request_irrigator(mut self, irrigator: impl RequestIrrigator + 'static) -> Self {
self.insert_request_irrigator(irrigator);
self
}
pub fn insert_request_irrigator_lambda(&mut self, func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = IrrigatorResult> + 'a>>) {
self.insert_request_irrigator(LambdaRequestIrrigator{ func });
}
pub fn with_request_irrigator_lambda(mut self, func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = IrrigatorResult> + 'a>>) -> Self {
self.insert_request_irrigator_lambda(func);
self
}
pub fn insert_response_irrigator(&mut self, irrigator: impl ResponseIrrigator + 'static) {
self.res_irrigators.push(Box::new(irrigator));
}
pub fn with_response_irrigator(mut self, irrigator: impl ResponseIrrigator + 'static) -> Self {
self.insert_response_irrigator(irrigator);
self
}
pub fn insert_response_irrigator_lambda(&mut self, func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, &'a mut DroughtResult, &'a Matches) -> Pin<Box<dyn Future<Output = ()> + 'a>>) {
self.insert_response_irrigator(LambdaResponseIrrigator{ func });
}
pub fn with_response_irrigator_lambda(mut self, func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, &'a mut DroughtResult, &'a Matches) -> Pin<Box<dyn Future<Output = ()> + 'a>>) -> Self {
self.insert_response_irrigator_lambda(func);
self
}
fn build_internal(self, root: Option<Arc<dyn Drought + 'static>>) -> Droughter {
Droughter {
root,
routes: {
let lowest_priority = self.routes.keys().reduce(|a,b| a.max(b)).cloned().unwrap_or(0);
let mut routes = vec![];
routes.resize_with(lowest_priority + 1, || Vec::new());
for (priority, items) in self.routes {
for route in items {
routes[priority].push(route.into());
}
}
routes
},
req_irrigators: self.req_irrigators.into_iter().map(Into::into).collect(),
res_irrigators: self.res_irrigators.into_iter().map(Into::into).collect()
}
}
pub fn build(self) -> Droughter {
self.build_internal(None)
}
pub fn build_with_root(self, root: impl Drought + 'static) -> Droughter {
self.build_internal(Some(Arc::new(root)))
}
pub fn build_with_lambda(self, root: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>>) -> Droughter {
self.build_with_root(LambdaDrought{ func: root })
}
}
impl Droughter {
pub async fn run(&self, req: &Request<Vec<u8>>) -> DroughterResult {
let path = {
let ogpath = req.uri().path().split("/").skip(1).map(uri_decode).collect::<Vec<_>>();
if req.uri() == "/" {
vec![]
}
else if ogpath.contains(&"".to_string()) || ogpath.contains(&"..".to_string()) {
let mut redirpath: Vec<String> = Vec::new();
for route in ogpath {
match route.as_str() {
"" => (),
".." => { redirpath.pop(); },
val => { redirpath.push(uri_encode(val)); }
}
}
return DroughterResult::Handle(
Response::builder()
.status(301)
.header("Location", "/".to_string() + &redirpath.join("/") + req.uri().query().unwrap_or(""))
.body(Vec::new()).unwrap()
)
} else { ogpath }
};
let map = Matches::new();
match self.run_internal(&path.clone(), &path, &req, map).await {
DroughtResult::NotFound => DroughterResult::Handle(
Response::builder()
.status(404)
.body(Vec::new())
.unwrap()
),
DroughtResult::Ignore => DroughterResult::Ignore,
DroughtResult::Drop => DroughterResult::Drop,
DroughtResult::DropWith(res) => DroughterResult::DropWith(res),
DroughtResult::Handle(res) => DroughterResult::Handle(res)
}
}
pub(self) async fn run_internal(&self, original_path: &Vec<String>, p_path: &Vec<String>, p_req: &Request<Vec<u8>>, mut map: Matches) -> DroughtResult {
use IrrigatorResult::*;
let mut abrupt_res = None;
let (path, req) = {
let mut path = None;
let mut req = None;
for req_irr in self.req_irrigators.iter() {
match req_irr.map(original_path, path.as_ref().unwrap_or(p_path), req.as_ref().unwrap_or(p_req), map.clone()).await {
ReplyNow(response) => {
abrupt_res = Some(response);
break;
},
ContinueWith(newpath, newreq) => {
path = Some(newpath);
req = Some(newreq);
},
ContinueWithPath(newpath) => {
path = Some(newpath);
},
ContinueWithReq(newreq) => {
req = Some(newreq);
},
Continue => ()
}
}
(path, req)
};
let (pathref, reqref) = (path.as_ref().unwrap_or(p_path), req.as_ref().unwrap_or(p_req));
let mut response = if let Some(res) = abrupt_res {
res
}
else {
if pathref.is_empty() {
if let Some(root) = &self.root {
root.handle(original_path, pathref, reqref, map.clone()).await
}
else {
DroughtResult::NotFound
}
}
else {
let mut res = DroughtResult::NotFound;
'main: for priority in self.routes.iter() {
for route in priority {
res = route.handle(original_path, pathref, reqref, map.clone()).await;
if let DroughtResult::NotFound = res {}
else { break 'main; }
}
}
res
}
};
for res_irr in self.res_irrigators.iter() {
res_irr.map(original_path, pathref, reqref, &mut response, &map).await;
}
response
}
}
#[derive(Clone)]
struct SubDrought {
path: String,
droughter: Droughter
}
impl Drought for SubDrought {
fn handle<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, map: Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>> {
Box::pin(
async {
if !path.is_empty() && path[0] == self.path {
self.droughter.run_internal(original_path, &path[1..].to_vec(), req, map).await
}
else { DroughtResult::NotFound }
}
)
}
}
#[derive(Clone)]
struct TypedDrought<T: FromStr> {
name: String,
droughter: Droughter,
_pd: PhantomData<T>
}
impl<T: FromStr> Drought for TypedDrought<T> {
fn handle<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, mut map: Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>> {
Box::pin(
async move {
if !path.is_empty() {
if let Ok(_) = T::from_str(&path[0]) {
map.insert(&self.name, &path[0]);
self.droughter.run_internal(original_path, &path[1..].to_vec(), req, map).await
}
else { DroughtResult::NotFound }
}
else { DroughtResult::NotFound }
}
)
}
}
#[derive(Clone)]
struct MatchedDrought {
name: String,
regex: Regex,
droughter: Droughter,
}
impl Drought for MatchedDrought {
fn handle<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, mut map: Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>> {
Box::pin(
async move {
if !path.is_empty() && self.regex.is_match(&path[0]) {
map.insert(&self.name, &path[0]);
self.droughter.run_internal(original_path, &path[1..].to_vec(), req, map).await
}
else { DroughtResult::NotFound }
}
)
}
}
#[derive(Clone)]
struct LambdaDrought {
func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>>
}
impl Drought for LambdaDrought {
fn handle<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, mut map: Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>> {
(self.func)(original_path, path, req, map)
}
}
#[derive(Clone)]
struct LambdaRequestIrrigator {
func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, Matches) -> Pin<Box<dyn Future<Output = IrrigatorResult> + 'a>>
}
impl RequestIrrigator for LambdaRequestIrrigator {
fn map<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, mut map: Matches) -> Pin<Box<dyn Future<Output = IrrigatorResult> + 'a>> {
(self.func)(original_path, path, req, map)
}
}
#[derive(Clone)]
struct LambdaResponseIrrigator {
func: for<'a> fn(&'a Vec<String>, &'a Vec<String>, &'a Request<Vec<u8>>, &'a mut DroughtResult, &'a Matches) -> Pin<Box<dyn Future<Output = ()> + 'a>>
}
impl ResponseIrrigator for LambdaResponseIrrigator {
fn map<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, res: &'a mut DroughtResult, mut map: &'a Matches) -> Pin<Box<dyn Future<Output = ()> + 'a>> {
(self.func)(original_path, path, req, res, map)
}
}
#[derive(Clone)]
pub struct Matches {
map: HashMap<String, String>
}
impl Matches {
pub(self) fn new() -> Self {
Self { map: HashMap::new() }
}
pub(self) fn insert(&mut self, k: &str, v: &String) {
self.map.insert(k.to_string(), v.clone());
}
pub fn get<T: FromStr>(&self, k: &str) -> Option<T> {
self.map.get(&k.to_string()).and_then(|val| val.parse().ok())
}
}
struct TestDrought;
impl Drought for TestDrought {
fn handle<'a>(&'a self, original_path: &'a Vec<String>, path: &'a Vec<String>, req: &'a Request<Vec<u8>>, mut map: Matches) -> Pin<Box<dyn Future<Output = DroughtResult> + 'a>> {
Box::pin(async{DroughtResult::NotFound})
}
}
#[cfg(feature = "test")]
#[test]
fn construction() {
use crate::simple;
use lazy_regex::{regex, Lazy};
let router_inner = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,_| { "Test".into() }));
let router = DroughterBuilder::new()
.with_drought(0, TestDrought)
.with_sub(3, "foo", router_inner.clone())
.with_typed::<usize>(4, "usize", router_inner.clone())
.with_matched(7, "regex", Lazy::force(regex!("^[A-Z]+$")).clone(), router_inner)
.build();
}
macro_rules! path_body_test {
($router:ident, $path:literal, $teststr:literal) => {
if let DroughterResult::Handle(res) = $router.run(&Request::builder().uri($path).body(vec![]).unwrap()).await {
assert_eq!(res.body(), $teststr);
} else { panic!("Testing {} yielded non-`Handle` value!", $path) }
}
}
#[cfg(feature = "test")]
#[test]
fn nesting() {
use crate::simple;
use lazy_regex::{regex, Lazy};
use DroughterResult::*;
let layer3_foo = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,_| { "/?/?/foo!".into() }));
let layer3_bar = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,_| { "/?/?/bar!".into() }));
let layer2_foo = DroughterBuilder::new()
.with_sub(1, "foo", layer3_foo.clone())
.with_sub(1, "bar", layer3_bar.clone())
.build_with_lambda(simple!(|_,_,_,_| { "/?/foo!".into() }));
let layer2_bar = DroughterBuilder::new()
.with_sub(1, "foo", layer3_foo.clone())
.with_sub(1, "bar", layer3_bar.clone())
.build_with_lambda(simple!(|_,_,_,_| { "/?/bar!".into() }));
let layer1_foo = DroughterBuilder::new()
.with_sub(1, "foo", layer2_foo.clone())
.with_sub(1, "bar", layer2_bar.clone())
.build_with_lambda(simple!(|_,_,_,_| { "/foo!".into() }));
let layer1_bar = DroughterBuilder::new()
.with_sub(1, "foo", layer2_foo.clone())
.with_sub(1, "bar", layer2_bar.clone())
.build_with_lambda(simple!(|_,_,_,_| { "/bar!".into() }));
let router = DroughterBuilder::new()
.with_sub(1, "foo", layer1_foo.clone())
.with_sub(1, "bar", layer1_bar.clone())
.build_with_lambda(simple!(|_,_,_,_| { "/!".into() }));
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
path_body_test!(router, "/", br#"/!"#);
path_body_test!(router, "/foo", br#"/foo!"#);
path_body_test!(router, "/bar", br#"/bar!"#);
path_body_test!(router, "/foo/foo", br#"/?/foo!"#);
path_body_test!(router, "/bar/foo", br#"/?/foo!"#);
path_body_test!(router, "/foo/bar", br#"/?/bar!"#);
path_body_test!(router, "/bar/bar", br#"/?/bar!"#);
path_body_test!(router, "/foo/foo/foo", br#"/?/?/foo!"#);
path_body_test!(router, "/bar/foo/foo", br#"/?/?/foo!"#);
path_body_test!(router, "/foo/bar/foo", br#"/?/?/foo!"#);
path_body_test!(router, "/bar/bar/foo", br#"/?/?/foo!"#);
path_body_test!(router, "/foo/foo/bar", br#"/?/?/bar!"#);
path_body_test!(router, "/bar/foo/bar", br#"/?/?/bar!"#);
path_body_test!(router, "/foo/bar/bar", br#"/?/?/bar!"#);
path_body_test!(router, "/bar/bar/bar", br#"/?/?/bar!"#);
})
}
#[cfg(feature = "test")]
#[test]
fn irrigators() {
use crate::{simple, lambda};
use lazy_regex::{regex, Lazy};
use DroughtResult::*;
use IrrigatorResult::*;
let layer1_foo = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,_| { "/foo!".into() }));
let layer1_bar = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,_| { "/bar!".into() }));
let router = DroughterBuilder::new()
.with_sub(1, "foo", layer1_foo.clone())
.with_sub(1, "bar", layer1_bar.clone())
.with_request_irrigator_lambda(lambda!(|_,_,_,_| { Continue }))
.with_request_irrigator_lambda(lambda!(|path,_,_,_| {
if path == &vec!["bar".to_string()] { ContinueWithPath(vec!["foo".to_string()]) }
else { Continue }
}))
.with_response_irrigator_lambda(lambda!(|_,_,_,dr,_| {
match dr {
Handle(ref mut res) => res.body_mut().push(b'~'),
_ => ()
}
}))
.build_with_lambda(simple!(|_,_,_,_| { "/!".into() }));
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
path_body_test!(router, "/", br#"/!~"#);
path_body_test!(router, "/foo", br#"/foo!~"#);
path_body_test!(router, "/bar", br#"/foo!~"#);
})
}
#[cfg(feature = "test")]
#[test]
fn matchers() {
use crate::simple;
use lazy_regex::{regex, Lazy};
use DroughterResult::*;
let router_usize = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,map| { format!("matched usize: {:?}", map.get::<usize>("usize")).into() }));
let router_regex2 = DroughterBuilder::new()
.build_with_lambda(simple!(|_,_,_,map| { format!("matched regexes: {:?}, {:?}", map.get::<String>("regex"), map.get::<String>("regex2")).into() }));
let router_regex = DroughterBuilder::new()
.with_matched(1, "regex2", Lazy::force(regex!("^[A-Z0-9]+$")).clone(), router_regex2)
.build_with_lambda(simple!(|_,_,_,map| { format!("matched regex: {:?}", map.get::<String>("regex")).into() }));
let router = DroughterBuilder::new()
.with_typed::<usize>(1, "usize", router_usize)
.with_matched(2, "regex", Lazy::force(regex!("^[A-Z]+$")).clone(), router_regex)
.build();
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
path_body_test!(router, "/TEST", br#"matched regex: Some("TEST")"#);
path_body_test!(router, "/TEST/TEST2", br#"matched regexes: Some("TEST"), Some("TEST2")"#);
path_body_test!(router, "/42", br#"matched usize: Some(42)"#);
path_body_test!(router, "/00000", br#"matched usize: Some(0)"#);
})
}