ferrum_router/recognizer/
mod.rs1use std::error::Error;
2use std::convert::AsRef;
3
4use ferrum::Handler;
5use regex::Regex;
6
7pub mod types;
8pub mod glob;
9pub mod matcher;
10pub use self::types::*;
11pub use self::glob::*;
12pub use self::matcher::*;
13
14pub type RecognizerResult<T = Recognizer> = Result<T, Box<Error>>;
15
16#[derive(Debug, PartialEq, Eq)]
17pub struct ParamChunk {
18 pub name: String,
19 pub start: usize,
20 pub end: usize,
21}
22
23pub struct Recognizer {
24 pub glob_regex: Regex,
25 pub param_chunks: Vec<ParamChunk>,
26 pub handler: Box<Handler>,
27}
28
29pub trait Recognize {
30 fn recognize<'a>(&'a self, path: &str) -> Option<RouteMatch<'a>>;
31}
32
33impl Recognizer {
34 pub fn new<G, N, P>(glob: G, handler: Box<Handler>, types: Option<&Store<N, P>>) -> RecognizerResult
35 where G: AsRef<[u8]>,
36 N: TypeName,
37 P: TypePattern
38 {
39 let types_default = DefaultStore::with_default_types();
40 let (glob_regex, param_chunks) = match types {
41 Some(types) => Recognizer::parse_glob(glob, types),
42 None => Recognizer::parse_glob(glob, &types_default)
43 }?;
44
45 Ok(Recognizer {
46 glob_regex,
47 param_chunks,
48 handler,
49 })
50 }
51
52 pub fn parse_glob<G, N, P>(glob: G, types: &Store<N, P>) -> RecognizerResult<(Regex, Vec<ParamChunk>)>
53 where G: AsRef<[u8]>,
54 N: TypeName,
55 P: TypePattern
56 {
57 let mut param_chunks = Vec::<ParamChunk>::new();
58 let mut pattern = "^".as_bytes().to_vec();
59
60 let identifier_regex = Regex::new("^[_a-zA-Z][_0-9a-zA-Z]*$").unwrap();
61
62 let mut iter = glob.as_ref().iter().enumerate();
63 while let Some((index, &bch)) = iter.next() {
64 match bch {
65 b'{' if index == 0 || glob.as_ref()[index - 1] != b'\\' => {
66 let mut param_name = Vec::new();
67 let mut param_type = Vec::new();
68 let mut is_type = false;
69 let start = index;
70
71 while let Some((index, &bch)) = iter.next() {
72 match bch {
73 b' ' | b'\t' | b'\r' | b'\n' => continue,
74 b':' if !is_type => is_type = true,
75 b'}' if index == 0 || glob.as_ref()[index - 1] != b'\\' => {
76 let end = index + 1;
77
78 if param_name.len() > 0 || param_type.len() > 0 {
79 let param_name = String::from_utf8(param_name)?;
80
81 let regex_chunk = if param_name.len() > 0 && !identifier_regex.is_match(param_name.as_str()) {
82 "{".to_string() + param_name.as_str() + "}"
83 } else {
84 let prefix = if param_name.len() > 0 {
85 let prefix = format!("(?P<{}>", param_name);
86 param_chunks.push(ParamChunk {
87 name: param_name.clone(),
88 start,
89 end
90 });
91 prefix
92 } else {
93 "(".to_string()
94 };
95
96 let param_type = String::from_utf8(param_type)?;
97
98 let regex_type = if param_type.len() > 0 {
99 if let Some(regex_pattern) = types.get(param_type.as_str()) {
100 regex_pattern.as_ref()
101 } else {
102 param_type.as_str()
103 }
104 } else {
105 if let Some(regex_pattern) = types.get(param_name.as_str()) {
106 regex_pattern.as_ref()
107 } else {
108 Type::STRING_PATTERN
109 }
110 };
111
112 prefix + regex_type + ")"
113 };
114 pattern.extend(regex_chunk.as_bytes().iter());
115 }
116 break;
117 },
118 _ if is_type => param_type.push(bch),
119 _ => param_name.push(bch),
120 }
121 }
122 },
123 _ => pattern.push(bch),
124 }
125 }
126 let mut pattern = String::from_utf8(pattern)?;
127 pattern += if pattern.chars().rev().next().unwrap_or('_') == '/' { "$" } else { "/?$" };
128 Ok((Regex::new(&pattern)?, param_chunks))
129 }
130}
131
132impl Recognize for Recognizer {
133 fn recognize<'a>(&'a self, path: &str) -> Option<RouteMatch<'a>> {
134 if let Some(captures) = self.glob_regex.captures(path) {
135 let mut params = Params::new();
136 for &ParamChunk { ref name, .. } in self.param_chunks.iter() {
137 if let Some(param_match) = captures.name(name) {
138 params.insert(name.clone(), param_match.as_str().to_string());
139 }
140 }
141 Some(RouteMatch::new(&self.handler, params))
142 } else {
143 None
144 }
145 }
146}
147
148impl<T> Recognize for Vec<T>
149 where T: AsRef<Recognizer>
150{
151 fn recognize<'a>(&'a self, path: &str) -> Option<RouteMatch<'a>> {
152 for recognizer in self {
153 if let Some(route_match) = recognizer.as_ref().recognize(path) {
154 return Some(route_match);
155 }
156 }
157 None
158 }
159}
160
161#[cfg(test)]
162mod tests;