From dcd117393a88dae37c1b2f01a6941c39e7337970 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Sat, 15 Jan 2022 23:23:25 -0800 Subject: [PATCH] Path management for the router Signed-off-by: Erik Hollensbe --- src/handler.rs | 17 +++++- src/lib.rs | 13 +---- src/path.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 src/path.rs diff --git a/src/handler.rs b/src/handler.rs index 337a65b..b83e37a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,10 +1,19 @@ -use std::sync::Arc; +use std::{collections::BTreeMap, sync::Arc}; -use crate::{HTTPResult, Params}; +use crate::HTTPResult; use async_trait::async_trait; use http::{Request, Response}; +#[derive(Debug, Clone)] +pub struct Params(BTreeMap); + +impl Default for Params { + fn default() -> Self { + Self(BTreeMap::default()) + } +} + pub type HandlerFunc = dyn Fn(Request, Params, Option>) -> HTTPResult + Sync; @@ -63,10 +72,12 @@ where } mod tests { - use crate::{Error, HTTPResult, Params}; + use crate::{Error, HTTPResult}; use http::{HeaderValue, Request, Response, StatusCode}; use hyper::Body; + use super::Params; + // this method adds a header: // wakka: wakka wakka // to the request. that's it! diff --git a/src/lib.rs b/src/lib.rs index 77c85be..55c84b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ pub mod handler; +pub mod path; +//pub mod router; use http::{Request, Response}; @@ -6,15 +8,6 @@ use crate::handler::BasicHandler; use std::collections::BTreeMap; -#[derive(Debug, Clone)] -pub struct Params(BTreeMap); - -impl Default for Params { - fn default() -> Self { - Self(BTreeMap::default()) - } -} - #[derive(Clone, Debug)] pub struct Error(String); @@ -43,5 +36,5 @@ pub type HTTPResult = Result<(Request, Option pub struct App { #[allow(dead_code)] // FIXME remove - routes: Vec<&'static BasicHandler>, + routes: BTreeMap, } diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..f6f190c --- /dev/null +++ b/src/path.rs @@ -0,0 +1,155 @@ +use std::collections::BTreeMap; + +use crate::Error; + +#[derive(Debug, Clone)] +pub enum RoutePart { + PathComponent(&'static str), + Param(&'static str), +} + +#[derive(Debug, Clone)] +pub struct Path(Vec); + +impl Path { + pub(crate) fn new(path: &'static str) -> Self { + let mut parts = Self::default(); + + for arg in path.split("/") { + if arg.starts_with(":") { + // is param + parts.push(RoutePart::Param(arg.trim_start_matches(":"))); + } else { + // is not param + parts.push(RoutePart::PathComponent(arg)); + } + } + + parts + } + + fn push(&mut self, arg: RoutePart) -> Self { + self.0.push(arg); + self.clone() + } + + fn params(&self) -> Vec<&str> { + let mut params = Vec::new(); + for arg in self.0.clone() { + match arg { + RoutePart::Param(p) => params.push(p), + _ => {} + } + } + + params + } + + fn extract(&self, provided: &'static str) -> Result, Error> { + let parts: Vec<&str> = provided.split("/").collect(); + let mut params = BTreeMap::new(); + + if parts.len() != self.0.len() { + return Err(Error::new("invalid parameters")); + } + + let mut i = 0; + + for part in self.0.clone() { + match part { + RoutePart::Param(p) => params.insert(p, parts[i]), + RoutePart::PathComponent(part) => { + if part != parts[i] { + return Err(Error::new("invalid path for parameter extraction")); + } + + None + } + }; + + i += 1 + } + + Ok(params) + } + + fn matches(&self, path: &'static str) -> bool { + let parts = path.split("/"); + + if parts.clone().count() != self.0.len() { + return false; + } + + let mut i = 0; + for arg in parts { + let res = match self.0[i] { + RoutePart::PathComponent(pc) => pc == arg, + RoutePart::Param(_param) => { + // FIXME advanced parameter shit here later + true + } + }; + + if !res { + return res; + } + + i += 1; + } + + true + } +} + +impl Default for Path { + fn default() -> Self { + Self(Vec::new()) + } +} + +impl ToString for Path { + fn to_string(&self) -> String { + let mut s = Vec::new(); + + for part in self.0.clone() { + s.push(match part { + RoutePart::PathComponent(pc) => pc.to_string(), + RoutePart::Param(param) => { + format!(":{}", param) + } + }); + } + + s.join("/") + } +} + +mod tests { + #[test] + fn test_path() { + use super::Path; + use std::collections::BTreeMap; + + let path = Path::new("/abc/def/ghi"); + assert!(path.matches("/abc/def/ghi")); + assert!(!path.matches("//abc/def/ghi")); + assert!(!path.matches("//def/ghi")); + assert!(path.params().is_empty()); + + let path = Path::new("/abc/:def/:ghi/jkl"); + assert!(!path.matches("/abc/def/ghi")); + assert!(path.matches("/abc/def/ghi/jkl")); + assert!(path.matches("/abc/ghi/def/jkl")); + assert!(path.matches("/abc/wooble/wakka/jkl")); + assert!(!path.matches("/nope/ghi/def/jkl")); + assert!(!path.matches("/abc/ghi/def/nope")); + + let mut bt = BTreeMap::new(); + bt.insert("def", "wooble"); + bt.insert("ghi", "wakka"); + + assert_eq!(path.extract("/abc/wooble/wakka/jkl").unwrap(), bt); + assert!(path.extract("/wooble/wakka/jkl").is_err()); + assert!(path.extract("/def/wooble/wakka/jkl").is_err()); + } +}