use std::collections::HashMap;
use std::collections::HashSet;
use regex::Regex;
use regex::quote as regex_quote;
use hyper::method::Method;
use http_errors::{HTTPError, MethodNotAllowed, NotFound};
use types::ViewArgs;
use utils::join_string;
fn parse_rule(rule: &str) -> Vec<(Option<&str>, &str)> {
let rule_re = Regex::new(r"(?x)
(?P<static>[^<]*) # static rule data
<
(?:
(?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # variable name
: # variable delimiter
)?
(?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name
>
").unwrap();
let mut rule_parts: Vec<(Option<&str>, &str)> = Vec::new();
let mut remaining = rule;
let mut used_names = HashSet::new();
while !remaining.is_empty() {
match rule_re.captures(remaining) {
Some(caps) => {
let static_part = caps.name("static");
if static_part.is_some() {
rule_parts.push((None, static_part.unwrap()));
}
let variable = caps.name("variable").unwrap();
let converter = match caps.name("converter") {
Some(converter) => { converter },
None => { "default" },
};
if used_names.contains(variable) {
panic!("variable name {} used twice.", variable);
}
used_names.insert(variable);
rule_parts.push((Some(converter), variable));
let end = caps.pos(0).unwrap().1;
let (_, tail) = remaining.split_at(end);
remaining = tail;
},
None => {
break;
}
}
}
if !remaining.is_empty() {
if remaining.contains('>') || remaining.contains('<') {
panic!("malformed url rule: {}", rule);
}
rule_parts.push((None, remaining));
}
rule_parts
}
#[derive(Clone)]
pub struct Matcher {
pub regex: Regex
}
impl Matcher {
pub fn new(regex: Regex) -> Matcher {
Matcher {
regex: regex
}
}
}
impl<'a> From<&'a str> for Matcher {
fn from(rule: &'a str) -> Matcher {
if !rule.starts_with('/') {
panic!("urls must start with a leading slash");
}
let is_branch = rule.ends_with('/');
let mut regex_parts: Vec<String> = Vec::new();
for (converter, variable) in parse_rule(rule.trim_right_matches('/')) {
match converter {
Some(converter) => {
let re = match converter {
"string" | "default" => "[^/]{1,}",
"int" => r"\d+",
"float" => r"\d+\.\d+",
"path" => "[^/].*?",
_ => { panic!("the converter {} does not exist", converter); }
};
regex_parts.push(format!("(?P<{}>{})", variable, re));
},
None => {
let escaped_variable = regex_quote(variable);
regex_parts.push(escaped_variable);
}
}
}
if is_branch {
regex_parts.push(String::from("(?P<__suffix__>/?)"));
}
let regex = format!(r"^{}$", join_string(regex_parts, ""));
Matcher::new(Regex::new(®ex).unwrap())
}
}
impl From<String> for Matcher {
fn from(rule: String) -> Matcher {
let rule_str: &str = &rule;
rule_str.into()
}
}
impl From<Regex> for Matcher {
fn from(regex: Regex) -> Matcher {
Matcher::new(regex)
}
}
pub struct RequestSlashError;
pub enum MapAdapterMatched {
MatchedRule((Rule, ViewArgs)),
MatchedRedirect((String, u16)),
MatchedError(HTTPError)
}
#[derive(Clone)]
pub struct Rule {
pub matcher: Matcher,
pub methods: HashSet<Method>,
pub endpoint: String,
pub provide_automatic_options: bool,
}
impl Rule {
pub fn new(matcher: Matcher, methods: &[Method], endpoint: &str) -> Rule {
let mut all_methods = HashSet::new();
for method in methods.iter() {
all_methods.insert(method.clone());
}
if all_methods.contains(&Method::Get) {
all_methods.insert(Method::Head);
}
let provide_automatic_options = if all_methods.contains(&Method::Options) {
false
} else {
all_methods.insert(Method::Options);
true
};
Rule {
matcher: matcher,
endpoint: endpoint.to_string(),
methods: all_methods,
provide_automatic_options: provide_automatic_options,
}
}
pub fn matched(&self, path: String) -> Option<Result<ViewArgs, RequestSlashError>> {
match self.matcher.regex.captures(&path) {
Some(caps) => {
if let Some(suffix) = caps.name("__suffix__") {
if suffix.is_empty() {
return Some(Err(RequestSlashError));
}
}
let mut view_args: HashMap<String, String> = HashMap::new();
for variable in self.matcher.regex.capture_names() {
if let Some(variable) = variable {
if variable != "__suffix__" {
view_args.insert(variable.to_string(), caps.name(variable).unwrap().to_string());
}
}
}
Some(Ok(view_args))
},
None => None,
}
}
}
#[derive(Clone)]
pub struct Map {
rules: Vec<Rule>,
}
impl Default for Map {
fn default() -> Map {
Map::new()
}
}
impl Map {
pub fn new() -> Map {
Map { rules: vec![] }
}
pub fn add(&mut self, rule: Rule) {
self.rules.push(rule);
}
pub fn bind(&self, host: String, path: String, query_string: Option<String>, method: Method) -> MapAdapter {
MapAdapter::new(self, host, path, query_string, method)
}
}
pub struct MapAdapter<'m> {
map: &'m Map,
url_scheme: String,
host: String,
path: String,
query_string: Option<String>,
method: Method,
}
impl<'m> MapAdapter<'m> {
pub fn new(map: &Map, host: String, path: String, query_string: Option<String>, method: Method) -> MapAdapter {
MapAdapter {
map: map,
url_scheme: "http".to_owned(),
host: host,
path: path,
query_string: query_string,
method: method,
}
}
fn make_redirect_url(&self) -> String {
let mut redirect_path = String::from("");
redirect_path = redirect_path + &self.path.trim_left_matches('/') + "/";
let mut suffix = String::from("");
if let Some(ref query_string) = self.query_string {
suffix = suffix + "?" + query_string;
}
format!("{}://{}/{}{}", self.url_scheme, self.host, redirect_path, suffix)
}
pub fn matched(&self) -> MapAdapterMatched {
let mut have_match_for = HashSet::new();
for rule in &self.map.rules {
let rule_view_args: ViewArgs;
match rule.matched(self.path.clone()) {
Some(result) => {
match result {
Ok(view_args) => {
rule_view_args = view_args;
},
Err(_) => {
let redirect_url = self.make_redirect_url();
return MapAdapterMatched::MatchedRedirect((redirect_url, 301));
}
}
},
None => { continue; },
}
if !rule.methods.contains(&self.method) {
for method in &rule.methods {
have_match_for.insert(method.clone());
}
continue;
}
return MapAdapterMatched::MatchedRule((rule.clone(), rule_view_args))
}
if !have_match_for.is_empty() {
let mut allowed_methods = Vec::new();
allowed_methods.extend(have_match_for.into_iter());
return MapAdapterMatched::MatchedError(MethodNotAllowed(Some(allowed_methods)))
}
MapAdapterMatched::MatchedError(NotFound)
}
pub fn allowed_methods(&self) -> Vec<Method> {
let mut have_match_for = HashSet::new();
for rule in &self.map.rules {
match rule.matched(self.path.clone()) {
Some(_) => {
for method in &rule.methods {
have_match_for.insert(method.clone());
}
continue;
},
None => { continue; },
}
}
let mut allowed_methods = Vec::new();
allowed_methods.extend(have_match_for.into_iter());
allowed_methods
}
}
#[test]
fn test_basic_routing() {
let mut map = Map::new();
map.add(Rule::new("/".into(), &[Method::Get], "index"));
map.add(Rule::new("/foo".into(), &[Method::Get], "foo"));
map.add(Rule::new("/bar/".into(), &[Method::Get], "bar"));
let adapter = map.bind(String::from("localhost"), String::from("/bar/"), None, Method::Get);
match adapter.matched() {
MapAdapterMatched::MatchedRule((rule, view_args)) => {
assert!(rule.methods.contains(&Method::Get));
assert!(!rule.methods.contains(&Method::Post));
assert!(rule.endpoint == String::from("bar"));
assert!(view_args.len() == 0);
},
_ => { panic!("Basic routing failed!"); }
}
}