diff --git a/.gitignore b/.gitignore index a9d37c560..44aabfb9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ target Cargo.lock +.idea/ + diff --git a/Cargo.toml b/Cargo.toml index cf316f198..6f638fdee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ rand = "0.3" linked-hash-map = "0.4" num_cpus = "1" crossbeam = "0.3" +regex = "0.2" [dev-dependencies] gotham_derive = { version = "0.1.0", path = "gotham_derive" } diff --git a/src/lib.rs b/src/lib.rs index f8a8523dd..25c7cfa4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ extern crate base64; extern crate rmp_serde; extern crate linked_hash_map; extern crate num_cpus; +extern crate regex; #[cfg(windows)] extern crate crossbeam; diff --git a/src/router/tree/node.rs b/src/router/tree/node.rs index e9118f6d5..d032ea4b2 100644 --- a/src/router/tree/node.rs +++ b/src/router/tree/node.rs @@ -4,12 +4,49 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::borrow::Borrow; use hyper::StatusCode; +use regex::Regex; use http::PercentDecoded; use router::route::{Route, Delegation}; use router::tree::{SegmentsProcessed, SegmentMapping, Path}; use state::{State, request_id}; +pub struct OrderedRegex(Regex); + +impl<'a> OrderedRegex { + pub fn new(regex: &'a str) -> Self { + OrderedRegex(Regex::new(&format!("^{pattern}$", pattern = regex)).unwrap()) + } +} + +impl PartialEq for OrderedRegex { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} + +impl Eq for OrderedRegex {} + +impl PartialOrd for OrderedRegex { + fn partial_cmp(&self, other: &OrderedRegex) -> Option { + return if self.0.as_str() > other.0.as_str() { + Some(Ordering::Greater) + } else if self.0.as_str() < other.0.as_str() { + Some(Ordering::Less) + } else if self.0.as_str() == other.0.as_str() { + Some(Ordering::Equal) + } else { + None + } + } +} + +impl Ord for OrderedRegex { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + /// Indicates the type of segment which is being represented by this Node. #[derive(PartialEq, Eq, PartialOrd, Ord)] pub enum SegmentType { @@ -22,7 +59,7 @@ pub enum SegmentType { /// Uses the supplied regex to determine match against incoming request paths. Constrained { /// Regex used to match against a single segment of a request path. - regex: String, + regex: OrderedRegex, }, /// Matches any corresponding segment for incoming request paths. @@ -263,8 +300,9 @@ impl Node { fn is_match(&self, req_path_segment: &PercentDecoded) -> bool { match self.segment_type { SegmentType::Static => self.segment == req_path_segment.val(), - // TODO #10, address Constrained type - SegmentType::Constrained { regex: _ } => unimplemented!(), + SegmentType::Constrained { ref regex } => { + regex.0.is_match(req_path_segment.val().as_ref()) + }, SegmentType::Dynamic | SegmentType::Glob => true, } } @@ -526,6 +564,17 @@ mod tests { seg3.add_child(seg4); root.add_child(seg3); + // Ensure regex matching works and that it's anchored to the segment and does not allow for + // overzealous matching + // GET: /resource/ where id: [0-9]+ + let mut seg_resource = NodeBuilder::new("resource", SegmentType::Static); + let mut seg_id = NodeBuilder::new("id", SegmentType::Constrained { + regex: OrderedRegex::new("[0-9]+") + }); + seg_id.add_route(get_route(pipeline_set.clone())); + seg_resource.add_child(seg_id); + root.add_child(seg_resource); + // Ensure traversal will backtrack and find the correct path if it goes down an ultimately // invalid branch, in this case seg6 initially being matched by the dynamic handler segdyn1 // which matches every segment it sees. @@ -616,6 +665,16 @@ mod tests { } None => panic!("traversal should have succeeded here"), } + + let rs = RequestPathSegments::new("/resource/5001"); + let expected_segment = "id"; + match root.traverse(&rs.segments()) { + Some((path, _, sp, _)) => { + assert_eq!(path.last().unwrap().segment(), expected_segment); + assert_eq!(sp, 2); + }, + None => panic!("traversal should have succeeded here"), + } } #[test]