ferrum_router/recognizer/
mod.rs

1use 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;