Router implementation and a few trait impls to make things happy

Signed-off-by: Erik Hollensbe <linux@hollensbe.org>
This commit is contained in:
Erik Hollensbe
2022-01-16 00:31:00 -08:00
parent 7c23829948
commit 1779cf13ca
4 changed files with 127 additions and 37 deletions
+25 -26
View File
@@ -5,17 +5,10 @@ use crate::HTTPResult;
use async_trait::async_trait;
use http::{Request, Response};
#[derive(Debug, Clone)]
pub struct Params(BTreeMap<String, String>);
impl Default for Params {
fn default() -> Self {
Self(BTreeMap::default())
}
}
pub(crate) type Params = BTreeMap<&'static str, &'static str>;
pub type HandlerFunc =
dyn Fn(Request<hyper::Body>, Params, Option<Response<hyper::Body>>) -> HTTPResult + Sync;
dyn Fn(Request<hyper::Body>, Option<Response<hyper::Body>>, Params) -> HTTPResult + Sync;
#[async_trait]
pub trait Handler
@@ -26,6 +19,7 @@ where
&self,
req: Request<hyper::Body>,
response: Option<Response<hyper::Body>>,
params: Params,
) -> HTTPResult;
}
@@ -34,7 +28,6 @@ pub struct BasicHandler
where
Self: Sync + Sized,
{
params: Params,
next: Option<Arc<BasicHandler>>,
func: &'static HandlerFunc,
}
@@ -43,12 +36,8 @@ impl BasicHandler
where
Self: Sync + Sized,
{
pub fn new(
params: Params,
next: Option<Arc<BasicHandler>>,
func: &'static HandlerFunc,
) -> Self {
Self { params, next, func }
pub fn new(next: Option<Arc<BasicHandler>>, func: &'static HandlerFunc) -> Self {
Self { next, func }
}
}
@@ -61,10 +50,16 @@ where
&self,
req: Request<hyper::Body>,
response: Option<Response<hyper::Body>>,
params: Params,
) -> HTTPResult {
let (req, response) = (*self.func)(req, self.params.clone(), response)?;
let (req, response) = (*self.func)(req, response, params.clone())?;
if self.next.is_some() {
return Ok(self.next.clone().unwrap().perform(req, response).await?);
return Ok(self
.next
.clone()
.unwrap()
.perform(req, response, params)
.await?);
}
Ok((req, response))
@@ -84,8 +79,8 @@ mod tests {
#[allow(dead_code)]
fn one(
mut req: Request<Body>,
_params: Params,
_response: Option<Response<Body>>,
_params: Params,
) -> HTTPResult {
let headers = req.headers_mut();
headers.insert("wakka", HeaderValue::from_str("wakka wakka").unwrap());
@@ -96,8 +91,8 @@ mod tests {
#[allow(dead_code)]
fn two(
req: Request<Body>,
_params: Params,
mut response: Option<Response<Body>>,
_params: Params,
) -> HTTPResult {
if let Some(header) = req.headers().get("wakka") {
if header != "wakka wakka" {
@@ -126,9 +121,9 @@ mod tests {
use std::sync::Arc;
// single stage handler that never yields a response
let bh = super::BasicHandler::new(Params::default(), None, &one);
let bh = super::BasicHandler::new(None, &one);
let req = Request::default();
let (req, response) = bh.perform(req, None).await.unwrap();
let (req, response) = bh.perform(req, None, Params::new()).await.unwrap();
if !req.headers().get("wakka").is_some() {
panic!("no wakkas")
}
@@ -138,15 +133,19 @@ mod tests {
}
// two-stage handler; yields a response if the first one was good.
let bh_two = super::BasicHandler::new(Params::default(), None, &two);
let bh = super::BasicHandler::new(Params::default(), Some(Arc::new(bh_two.clone())), &one);
let (_, response) = bh.perform(req, None).await.unwrap();
let bh_two = super::BasicHandler::new(None, &two);
let bh = super::BasicHandler::new(Some(Arc::new(bh_two.clone())), &one);
let (_, response) = bh.perform(req, None, Params::new()).await.unwrap();
if !(response.is_some() && response.unwrap().status() == StatusCode::OK) {
panic!("response not ok")
}
if !bh_two.perform(Request::default(), None).await.is_err() {
if !bh_two
.perform(Request::default(), None, Params::new())
.await
.is_err()
{
panic!("no error")
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
pub mod handler;
pub mod path;
//pub mod router;
pub mod router;
use http::{Request, Response};
+19 -10
View File
@@ -1,17 +1,29 @@
use std::collections::BTreeMap;
use crate::{handler::Params, Error};
use crate::Error;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialOrd, PartialEq)]
pub enum RoutePart {
PathComponent(&'static str),
Param(&'static str),
Leader,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialOrd)]
pub struct Path(Vec<RoutePart>);
impl PartialEq for Path {
fn eq(&self, other: &Self) -> bool {
self.to_string() == other.to_string()
}
}
impl Eq for Path {}
impl Ord for Path {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_string().cmp(&other.to_string())
}
}
impl Path {
pub(crate) fn new(path: &'static str) -> Self {
let mut parts = Self::default();
@@ -47,12 +59,9 @@ impl Path {
params
}
pub(crate) fn extract(
&self,
provided: &'static str,
) -> Result<BTreeMap<&'static str, &str>, Error> {
pub(crate) fn extract(&self, provided: &'static str) -> Result<Params, Error> {
let parts: Vec<&str> = provided.split("/").collect();
let mut params = BTreeMap::new();
let mut params = Params::default();
if parts.len() != self.0.len() {
return Err(Error::new("invalid parameters"));
+82
View File
@@ -0,0 +1,82 @@
use std::collections::{BTreeMap, BTreeSet, HashSet};
use http::Request;
use crate::{
handler::{BasicHandler, Handler},
path::Path,
Error, HTTPResult,
};
#[derive(Clone)]
pub struct Route {
method: http::Method,
path: Path,
handler: BasicHandler,
}
impl PartialEq for Route {
fn eq(&self, other: &Self) -> bool {
let left = self.method.to_string() + " " + &self.path.to_string();
let right = other.method.to_string() + " " + &other.path.to_string();
left == right
}
}
impl Eq for Route {}
impl PartialOrd for Route {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Route {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let left = self.method.to_string() + " " + &self.path.to_string();
let right = other.method.to_string() + " " + &other.path.to_string();
left.to_string().cmp(&right.to_string())
}
}
impl Route {
fn new(method: http::Method, path: &'static str, handler: BasicHandler) -> Self {
Self {
method,
handler,
path: Path::new(path),
}
}
async fn dispatch(&self, provided: &'static str, req: Request<hyper::Body>) -> HTTPResult {
let params = self.path.extract(provided)?;
self.handler.perform(req, None, params).await
}
}
#[derive(Clone)]
pub struct Router(BTreeSet<Route>);
impl Router {
pub fn new() -> Self {
Self(BTreeSet::new())
}
pub fn add(&mut self, method: http::Method, path: &'static str, bh: BasicHandler) -> Self {
self.0.insert(Route::new(method, path, bh));
self.clone()
}
pub fn find(&self, req: &'static Request<hyper::Body>) -> Result<BasicHandler, Error> {
let path = req.uri().path();
for route_path in self.0.clone() {
if route_path.path.matches(path) && route_path.method.eq(req.method()) {
return Ok(route_path.handler);
}
}
Err(Error::new("no route found for request"))
}
}