use fancy_regex::Regex;
use http_types::{Method, Url};
use std::collections::HashMap;
use std::sync::Arc;
mod segment;
use segment::{Analysis, Segment, SegmentRecord};
lazy_static! {
static ref RE_PARSE_PARAM: Regex = Regex::new(r"((?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})+)|(?:\{(\w+)(?:(\*)(\d+)?)?(\?)?\})").unwrap();
static ref RE_VALIDATE_PATH: Regex = Regex::new(r"(?:^\/$)|(?:^(?:\/(?:(?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})+|(?:\{\w+(?:\*[1-9]\d*)?\})|(?:(?:(?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})+(?:\{\w+\??\}))+(?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})*)|(?:(?:\{\w+\??\})(?:(?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})+(?:\{\w+\??\}))+(?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})*)|(?:(?:\{\w+\??\})(?:[\w\!\$&'\(\)\*\+\,;\=\:@\-\.~]|%[A-F0-9]{2})+)))*(?:\/(?:\{\w+(?:(?:\*(?:[1-9]\d*)?)|(?:\?))?\})?)?$)").unwrap();
static ref RE_VALIDATE_PATH_ENCODED: Regex = Regex::new(r"%(?:2[146-9A-E]|3[\dABD]|4[\dA-F]|5[\dAF]|6[1-9A-F]|7[\dAE])").unwrap();
}
#[derive(Debug, Clone)]
pub struct RouteConfig {
pub vhost: String,
}
#[derive(Debug, Clone)]
struct Entry {
routes: Vec<String>, // TODO: Change to handlers
segment: Segment,
}
struct RouterInner {
table: HashMap<Method, Entry>,
}
#[derive(Clone)]
pub struct Router {
inner: Arc<RouterInner>,
}
impl Router {
pub fn new() -> Self {
Router {
inner: Arc::new(RouterInner {
table: HashMap::new(),
}),
}
}
pub fn add(&mut self, method: Method, path: impl AsRef<str>) -> &mut Self {
self.add_with_config(method, path, None)
}
pub fn add_with_config(
&mut self,
method: Method,
path: impl AsRef<str>,
config: Option<RouteConfig>,
) -> &mut Router {
let analysis = self.analyze(path);
let entry = self.mut_inner().table.entry(method).or_insert(Entry {
routes: vec![],
segment: Segment::new(),
});
entry.segment.add(analysis);
self
}
pub fn route(self, method: Method, url: &Url) -> String {
let segments = if url.path().len() == 1 {
vec![""]
} else {
let mut parts: Vec<&str> = url.path().split("/").collect();
parts.split_off(1)
};
match self.inner.table.get(&method) {
None => "".to_string(),
Some(_entry) => {
return url.path().to_string();
}
}
}
//fn lookup<'a>(self, path: &'a str, segments: Vec<&'a str>, records: Vec<Record>)
fn mut_inner(&mut self) -> &mut RouterInner {
Arc::get_mut(&mut self.inner).expect("error obtaining mutable router")
}
fn analyze<S: AsRef<str>>(&mut self, path: S) -> Analysis {
RE_VALIDATE_PATH.is_match(path.as_ref()).unwrap();
let path_parts = path.as_ref().split("/");
let mut fingers = vec![];
let params: Vec<SegmentRecord> = vec![];
let mut segments: Vec<SegmentRecord> = vec![];
// TODO: skip first path_part
for path_part in path_parts {
let path_part = path_part.to_lowercase();
// Literal
if path_part.find("{") == None {
let literal = path_part.clone();
fingers.push(path_part);
segments.push(SegmentRecord::new().with_literal(literal));
continue;
}
// Parameter
let parts = parse_params(path_part);
}
let analysis = Analysis {
path: path.as_ref().to_string(),
segments,
params: vec![],
fingerprint: format!("/{}", fingers.join("/")),
};
analysis
}
}
fn parse_params(part: String) -> Vec<SegmentRecord> {
let mut parts: Vec<SegmentRecord> = vec![];
// groups are: 0: literal, 1: name, 2: wildcard, 3: count, 4: empty
match RE_PARSE_PARAM.captures(part.as_str()) {
Ok(captures) => match captures {
Some(captures) => match captures.get(0) {
Some(literal) => {
parts.push(SegmentRecord::new().with_literal(literal.as_str().to_string()));
}
None => {
let mut record = SegmentRecord::new();
record = match captures.get(1) {
Some(name) => record.with_name(name.as_str().to_string()),
None => record,
};
record = match captures.get(2) {
Some(_wildcard) => record.with_wildcard(true),
None => record.with_wildcard(false),
};
record = match captures.get(3) {
Some(count) => record.with_count(count.as_str().to_string().parse::<i32>().unwrap()),
None => record,
};
record = match captures.get(2) {
Some(_empty) => record.with_empty(true),
None => record.with_empty(false),
};
parts.push(record);
}
},
None => {}
},
Err(_) => {}
}
parts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_add_route() {
let mut router = Router::new();
router.add(Method::Get, "/test");
}
#[test]
fn fails_invalid_path() {
let mut router = Router::new();
router.add(Method::Get, "/{p}{x}b");
}
#[test]
fn can_route() {
let mut router = Router::new();
router.add(Method::Get, "/test");
let url = Url::parse("http://test.com/test");
assert!(!url.is_err());
let found = router.route(Method::Get, &url.unwrap());
assert_eq!(found, "/test");
}
#[test]
fn check_path_regex() {
let invalids = vec!["path", "/%path/", "/path/{param*}/to"];
for invalid in invalids {
let result = RE_VALIDATE_PATH.is_match(invalid);
assert!(!result.unwrap());
}
let valids = vec!["/", "/path", "/path/"];
for valid in valids {
let result = RE_VALIDATE_PATH.is_match(valid);
assert!(result.unwrap());
}
}
}