mirror of
https://github.com/zerotier/ratpack.git
synced 2026-05-22 16:27:23 -07:00
Path management for the router
Signed-off-by: Erik Hollensbe <linux@hollensbe.org>
This commit is contained in:
+14
-3
@@ -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<String, String>);
|
||||
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
Self(BTreeMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub type HandlerFunc =
|
||||
dyn Fn(Request<hyper::Body>, Params, Option<Response<hyper::Body>>) -> 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!
|
||||
|
||||
+3
-10
@@ -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<String, String>);
|
||||
|
||||
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<hyper::Body>, Option<Response<hyper::Body>
|
||||
|
||||
pub struct App {
|
||||
#[allow(dead_code)] // FIXME remove
|
||||
routes: Vec<&'static BasicHandler>,
|
||||
routes: BTreeMap<String, &'static BasicHandler>,
|
||||
}
|
||||
|
||||
+155
@@ -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<RoutePart>);
|
||||
|
||||
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<BTreeMap<&'static str, &str>, 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user