diff --git a/lib/src/http.rs b/lib/src/http.rs index 4f1fa6162..2af659a05 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -948,22 +948,28 @@ impl Session { .borrow() .listeners .get(&self.listener_token) + .with_context(|| "No listener found for this request")? // very unlikely .as_ref() - .and_then(|listener| listener.borrow().frontend_from_request(host, uri, method)); + .borrow() + .frontend_from_request(host, uri, method); - let cluster_id = match cluster_id_res { - Some(Route::ClusterId(cluster_id)) => cluster_id, - Some(Route::Deny) => { - self.set_answer(DefaultAnswerStatus::Answer401, None); - bail!("Unauthorized route"); - } - None => { - let no_host_error = format!("Host not found: {}", host); + let route = match cluster_id_res { + Ok(route) => route, + Err(e) => { + let no_host_error = format!("Host not found: {}: {:#}", host, e); self.set_answer(DefaultAnswerStatus::Answer404, None); bail!(no_host_error); } }; + let cluster_id = match route { + Route::ClusterId(cluster_id) => cluster_id, + Route::Deny => { + self.set_answer(DefaultAnswerStatus::Answer401, None); + bail!("Unauthorized route"); + } + }; + let front_should_redirect_https = self .proxy .borrow() @@ -1572,54 +1578,58 @@ impl Listener { Some(self.token) } - // TODO: return Result with context - pub fn add_http_front(&mut self, http_front: HttpFrontend) -> Result<(), String> { - if self.fronts.add_http_front(http_front) { - Ok(()) - } else { - Err(String::from("could not add HTTP front")) - } + pub fn add_http_front(&mut self, http_front: HttpFrontend) -> anyhow::Result<()> { + self.fronts + .add_http_front(&http_front) + .with_context(|| format!("Could not add http frontend {:?}", http_front)) } - // TODO: return Result with context - pub fn remove_http_front(&mut self, http_front: HttpFrontend) -> Result<(), String> { + pub fn remove_http_front(&mut self, http_front: HttpFrontend) -> anyhow::Result<()> { debug!("removing http_front {:?}", http_front); - //FIXME: proper error reporting - if !self.fronts.remove_http_front(http_front) { - return Err(String::from("could not remove HTTP front")); - } - Ok(()) + self.fronts + .remove_http_front(&http_front) + .with_context(|| format!("Could not remove http frontend {:?}", http_front)) } - // TODO: return Result with context - pub fn frontend_from_request(&self, host: &str, uri: &str, method: &Method) -> Option { - // redundant - // already called once in extract_route - let host: &str = if let Ok((i, (hostname, _))) = hostname_and_port(host.as_bytes()) { - if i != &b""[..] { - error!( - "frontend_from_request: invalid remaining chars after hostname. Host: {}", - host + // redundant, already called once in extract_route + pub fn frontend_from_request( + &self, + host: &str, + uri: &str, + method: &Method, + ) -> anyhow::Result { + let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) { + Ok(tuple) => tuple, + Err(parse_error) => { + // parse_error contains a slice of given_host, which should NOT escape this scope + bail!( + "Hostname parsing failed for host {}: {}", + host.clone(), + parse_error, ); - return None; } + }; + if remaining_input != &b""[..] { + bail!( + "frontend_from_request: invalid remaining chars after hostname. Host: {}", + host + ); + } - /*if port == Some(&b"80"[..]) { - // it is alright to call from_utf8_unchecked, - // we already verified that there are only ascii - // chars in there - unsafe { from_utf8_unchecked(hostname) } - } else { - host - } - */ - unsafe { from_utf8_unchecked(hostname) } + /*if port == Some(&b"80"[..]) { + // it is alright to call from_utf8_unchecked, + // we already verified that there are only ascii + // chars in there + unsafe { from_utf8_unchecked(hostname) } } else { - error!("hostname parsing failed for: '{}'", host); - return None; - }; + host + } + */ + let host = unsafe { from_utf8_unchecked(hostname) }; - self.fronts.lookup(host.as_bytes(), uri.as_bytes(), method) + self.fronts + .lookup(host.as_bytes(), uri.as_bytes(), method) + .with_context(|| "No cluster found") } fn accept(&mut self) -> Result { @@ -1641,6 +1651,7 @@ impl Listener { } impl ProxyConfiguration for Proxy { + // TODO: put each pattern matching arm in its own method fn notify(&mut self, message: ProxyRequest) -> ProxyResponse { // ToDo temporary //trace!("{} notified", message); @@ -1668,12 +1679,11 @@ impl ProxyConfiguration for Proxy { let tags = front.tags.to_owned(); match owned.add_http_front(front) { - Ok(_) => { + Ok(()) => { owned.set_tags(hostname, tags); - ProxyResponse::ok(message.id) } - Err(err) => ProxyResponse::error(message.id, err), + Err(err) => ProxyResponse::error(message.id, format!("{:#}", err)), // because anyhow } } else { ProxyResponse::error( @@ -1701,7 +1711,7 @@ impl ProxyConfiguration for Proxy { owned.set_tags(hostname, None); ProxyResponse::ok(message.id) } - Err(err) => ProxyResponse::error(message.id, err), + Err(err) => ProxyResponse::error(message.id, format!("{:#}", err)), // because anyhow } } else { ProxyResponse::error( @@ -2312,42 +2322,50 @@ mod tests { let uri3 = "/yolo/swag".to_owned(); let mut fronts = Router::new(); - fronts.add_http_front(HttpFrontend { - route: Route::ClusterId(cluster_id1), - address: "0.0.0.0:80".parse().unwrap(), - hostname: "lolcatho.st".to_owned(), - path: PathRule::Prefix(uri1), - method: None, - position: RulePosition::Tree, - tags: None, - }); - fronts.add_http_front(HttpFrontend { - route: Route::ClusterId(cluster_id2), - address: "0.0.0.0:80".parse().unwrap(), - hostname: "lolcatho.st".to_owned(), - path: PathRule::Prefix(uri2), - method: None, - position: RulePosition::Tree, - tags: None, - }); - fronts.add_http_front(HttpFrontend { - route: Route::ClusterId(cluster_id3), - address: "0.0.0.0:80".parse().unwrap(), - hostname: "lolcatho.st".to_owned(), - path: PathRule::Prefix(uri3), - method: None, - position: RulePosition::Tree, - tags: None, - }); - fronts.add_http_front(HttpFrontend { - route: Route::ClusterId("cluster_1".to_owned()), - address: "0.0.0.0:80".parse().unwrap(), - hostname: "other.domain".to_owned(), - path: PathRule::Prefix("/test".to_owned()), - method: None, - position: RulePosition::Tree, - tags: None, - }); + fronts + .add_http_front(&HttpFrontend { + route: Route::ClusterId(cluster_id1), + address: "0.0.0.0:80".parse().unwrap(), + hostname: "lolcatho.st".to_owned(), + path: PathRule::Prefix(uri1), + method: None, + position: RulePosition::Tree, + tags: None, + }) + .expect("Could not add http frontend"); + fronts + .add_http_front(&HttpFrontend { + route: Route::ClusterId(cluster_id2), + address: "0.0.0.0:80".parse().unwrap(), + hostname: "lolcatho.st".to_owned(), + path: PathRule::Prefix(uri2), + method: None, + position: RulePosition::Tree, + tags: None, + }) + .expect("Could not add http frontend"); + fronts + .add_http_front(&HttpFrontend { + route: Route::ClusterId(cluster_id3), + address: "0.0.0.0:80".parse().unwrap(), + hostname: "lolcatho.st".to_owned(), + path: PathRule::Prefix(uri3), + method: None, + position: RulePosition::Tree, + tags: None, + }) + .expect("Could not add http frontend"); + fronts + .add_http_front(&HttpFrontend { + route: Route::ClusterId("cluster_1".to_owned()), + address: "0.0.0.0:80".parse().unwrap(), + hostname: "other.domain".to_owned(), + path: PathRule::Prefix("/test".to_owned()), + method: None, + position: RulePosition::Tree, + tags: None, + }) + .expect("Could not add http frontend"); let address: SocketAddr = FromStr::from_str("127.0.0.1:1030").expect("could not parse address"); @@ -2386,6 +2404,6 @@ mod tests { frontend4.expect("should find frontend"), Route::ClusterId("cluster_3".to_string()) ); - assert_eq!(frontend5, None); + assert!(frontend5.is_err()); } } diff --git a/lib/src/https.rs b/lib/src/https.rs index 4a4f0635b..ce5f9d7a6 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -1227,25 +1227,31 @@ impl Session { } }; - let route_res = self + let cluster_id_res = self .proxy .borrow() .listeners .get(&self.listener_token) + .with_context(|| "No listener found for this request")? // very unlikely .as_ref() - .and_then(|listener| listener.borrow().frontend_from_request(host, uri, method)); + .borrow() + .frontend_from_request(host, uri, method); - match route_res { - Some(Route::ClusterId(cluster_id)) => Ok(cluster_id), - Some(Route::Deny) => { - self.set_answer(DefaultAnswerStatus::Answer401, None); - bail!("Route is unauthorized"); - } - None => { - let no_host_error = format!("Host not found: {}", host); + let route = match cluster_id_res { + Ok(route) => route, + Err(e) => { + let no_host_error = format!("Host not found: {}: {:#}", host, e); self.set_answer(DefaultAnswerStatus::Answer404, None); bail!(no_host_error); } + }; + + match route { + Route::ClusterId(cluster_id) => Ok(cluster_id), + Route::Deny => { + self.set_answer(DefaultAnswerStatus::Answer401, None); + bail!("Route is unauthorized"); + } } } @@ -1797,37 +1803,53 @@ impl Listener { Ok(server_config) } - pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> bool { - self.fronts.add_http_front(tls_front) + pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> anyhow::Result<()> { + self.fronts + .add_http_front(&tls_front) + .with_context(|| "Could not add https frontend") } - pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> bool { + pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> anyhow::Result<()> { debug!("removing tls_front {:?}", tls_front); - self.fronts.remove_http_front(tls_front) + self.fronts + .remove_http_front(&tls_front) + .with_context(|| "Could not remove https frontend") } - // TODO: return Result with context - // ToDo factor out with http.rs - pub fn frontend_from_request(&self, host: &str, uri: &str, method: &Method) -> Option { - let host: &str = if let Ok((i, (hostname, _))) = hostname_and_port(host.as_bytes()) { - if i != &b""[..] { - error!( - "frontend_from_request: invalid remaining chars after hostname. Host: {}", - host + // TODO factor out with http.rs + pub fn frontend_from_request( + &self, + host: &str, + uri: &str, + method: &Method, + ) -> anyhow::Result { + let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) { + Ok(tuple) => tuple, + Err(parse_error) => { + // parse_error contains a slice of given_host, which should NOT escape this scope + bail!( + "Hostname parsing failed for host {}: {}", + host.clone(), + parse_error, ); - return None; } - - // it is alright to call from_utf8_unchecked, - // we already verified that there are only ascii - // chars in there - unsafe { from_utf8_unchecked(hostname) } - } else { - error!("hostname parsing failed for: '{}'", host); - return None; }; - self.fronts.lookup(host.as_bytes(), uri.as_bytes(), method) + if remaining_input != &b""[..] { + bail!( + "frontend_from_request: invalid remaining chars after hostname. Host: {}", + host + ); + } + + // it is alright to call from_utf8_unchecked, + // we already verified that there are only ascii + // chars in there + let host = unsafe { from_utf8_unchecked(hostname) }; + + self.fronts + .lookup(host.as_bytes(), uri.as_bytes(), method) + .with_context(|| "No cluster found") } fn accept(&mut self) -> Result { @@ -2038,6 +2060,7 @@ impl ProxyConfiguration for Proxy { fn notify(&mut self, message: ProxyRequest) -> ProxyResponse { //trace!("{} notified", message); + // TODO: put each pattern matching arm in its own method match message.order { ProxyRequestOrder::AddCluster(cluster) => { debug!("{} add cluster {:?}", message.id, cluster); @@ -2057,9 +2080,14 @@ impl ProxyConfiguration for Proxy { .find(|l| l.borrow().address == front.address) { let mut owned = listener.borrow_mut(); - owned.set_tags(front.hostname.to_owned(), front.tags.to_owned()); - owned.add_https_front(front); - ProxyResponse::ok(message.id) + let (hostname, tags) = (front.hostname.to_owned(), front.tags.to_owned()); + match owned.add_https_front(front) { + Ok(()) => { + owned.set_tags(hostname, tags); + ProxyResponse::ok(message.id) + } + Err(err) => ProxyResponse::error(message.id, format!("{:#}", err)), // because anyhow + } } else { ProxyResponse::error( message.id, @@ -2075,9 +2103,14 @@ impl ProxyConfiguration for Proxy { .find(|l| l.borrow_mut().address == front.address) { let mut owned = listener.borrow_mut(); - owned.set_tags(front.hostname.to_owned(), None); - owned.remove_https_front(front); - ProxyResponse::ok(message.id) + let hostname = front.hostname.to_owned(); + match owned.remove_https_front(front) { + Ok(_) => { + owned.set_tags(hostname, None); + ProxyResponse::ok(message.id) + } + Err(err) => ProxyResponse::error(message.id, format!("{:#}", err)), // because anyhow + } } else { ProxyResponse::error( message.id, @@ -2427,27 +2460,27 @@ mod tests { let mut fronts = Router::new(); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), - PathRule::Prefix(uri1), - MethodRule::new(None), - Route::ClusterId(cluster_id1.clone()) + &PathRule::Prefix(uri1), + &MethodRule::new(None), + &Route::ClusterId(cluster_id1.clone()) )); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), - PathRule::Prefix(uri2), - MethodRule::new(None), - Route::ClusterId(cluster_id2) + &PathRule::Prefix(uri2), + &MethodRule::new(None), + &Route::ClusterId(cluster_id2) )); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), - PathRule::Prefix(uri3), - MethodRule::new(None), - Route::ClusterId(cluster_id3) + &PathRule::Prefix(uri3), + &MethodRule::new(None), + &Route::ClusterId(cluster_id3) )); assert!(fronts.add_tree_rule( "other.domain".as_bytes(), - PathRule::Prefix("test".to_string()), - MethodRule::new(None), - Route::ClusterId(cluster_id1) + &PathRule::Prefix("test".to_string()), + &MethodRule::new(None), + &Route::ClusterId(cluster_id1) )); let address: StdSocketAddr = FromStr::from_str("127.0.0.1:1032") @@ -2507,7 +2540,7 @@ mod tests { ); println!("TEST {}", line!()); let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get); - assert_eq!(frontend5, None); + assert!(frontend5.is_err()); // assert!(false); } diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index c6e6072ae..6f5f9dcde 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -1,6 +1,7 @@ pub mod pattern_trie; pub mod trie; +use anyhow::{bail, Context}; use regex::bytes::Regex; use std::str::from_utf8; @@ -32,7 +33,6 @@ impl Router { } } - // TODO: return Result with context pub fn lookup(&self, hostname: &[u8], path: &[u8], method: &Method) -> Option { for (domain_rule, path_rule, method_rule, cluster_id) in &self.pre { if domain_rule.matches(hostname) @@ -96,76 +96,74 @@ impl Router { None } - pub fn add_http_front(&mut self, front: HttpFrontend) -> bool { - match front.position { - RulePosition::Pre => match ( - front.hostname.parse::(), - PathRule::from_config(front.path), - ) { - (Ok(domain), Some(path)) => { - self.add_pre_rule(domain, path, MethodRule::new(front.method), front.route) - } - _ => false, - }, - RulePosition::Post => match ( - front.hostname.parse::(), - PathRule::from_config(front.path), - ) { - (Ok(domain), Some(path)) => { - self.add_post_rule(domain, path, MethodRule::new(front.method), front.route) - } - _ => false, + pub fn add_http_front(&mut self, front: &HttpFrontend) -> anyhow::Result<()> { + let path_rule = PathRule::from_config(front.path.clone()).with_context(|| { + format!( + "Could not parse path rulo from frontend path {}", + front.path + ) + })?; + + let method_rule = MethodRule::new(front.method.clone()); + + let success = match front.position { + RulePosition::Pre => match front.hostname.parse::() { + Ok(domain) => self.add_pre_rule(&domain, &path_rule, &method_rule, &front.route), + Err(e) => bail!("Parsing hostname {} failed: {:?}", front.hostname, e), }, - RulePosition::Tree => match PathRule::from_config(front.path) { - Some(path) => self.add_tree_rule( - front.hostname.as_bytes(), - path, - MethodRule::new(front.method), - front.route, - ), - _ => false, + RulePosition::Post => match front.hostname.parse::() { + Ok(domain) => self.add_post_rule(&domain, &path_rule, &method_rule, &front.route), + Err(e) => bail!("Parsing hostname {} failed: {:?}", front.hostname, e), }, + RulePosition::Tree => self.add_tree_rule( + front.hostname.as_bytes(), + &path_rule, + &method_rule, + &front.route, + ), + }; + match success { + true => Ok(()), + false => bail!("Could not add rule to the router"), } } - pub fn remove_http_front(&mut self, front: HttpFrontend) -> bool { - match front.position { - RulePosition::Pre => match ( - front.hostname.parse::(), - PathRule::from_config(front.path), - ) { - (Ok(domain), Some(path)) => { - self.remove_pre_rule(domain, path, MethodRule::new(front.method)) - } - _ => false, - }, - RulePosition::Post => match ( - front.hostname.parse::(), - PathRule::from_config(front.path), - ) { - (Ok(domain), Some(path)) => { - self.remove_post_rule(domain, path, MethodRule::new(front.method)) - } - _ => false, + pub fn remove_http_front(&mut self, front: &HttpFrontend) -> anyhow::Result<()> { + let path_rule = PathRule::from_config(front.path.clone()).with_context(|| { + format!( + "Could not parse path rulo from frontend path {}", + front.path + ) + })?; + + let method_rule = MethodRule::new(front.method.clone()); + + let remove_success = match front.position { + RulePosition::Pre => match front.hostname.parse::() { + Ok(domain) => self.remove_pre_rule(&domain, &path_rule, &method_rule), + Err(e) => bail!("Parsing hostname {} failed: {:?}", front.hostname, e), }, - RulePosition::Tree => match PathRule::from_config(front.path) { - Some(path) => self.remove_tree_rule( - front.hostname.as_bytes(), - path, - MethodRule::new(front.method), - front.route, - ), - _ => false, + RulePosition::Post => match front.hostname.parse::() { + Ok(domain) => self.remove_post_rule(&domain, &path_rule, &method_rule), + Err(e) => bail!("Parsing hostname {} failed: {:?}", front.hostname, e), }, + RulePosition::Tree => { + self.remove_tree_rule(front.hostname.as_bytes(), &path_rule, &method_rule) + } + }; + + match remove_success { + true => Ok(()), + false => bail!("could not remove the rule from router"), } } pub fn add_tree_rule( &mut self, hostname: &[u8], - path: PathRule, - method: MethodRule, - cluster_id: Route, + path: &PathRule, + method: &MethodRule, + cluster: &Route, ) -> bool { let hostname = match from_utf8(hostname) { Err(_) => return false, @@ -180,15 +178,17 @@ impl Router { self.tree.domain_lookup_mut(hostname.as_bytes(), false) { empty = false; - if !paths.iter().any(|(p, m, _)| *p == path && *m == method) { - paths.push((path, method, cluster_id)); + if !paths.iter().any(|(p, m, _)| p == path && m == method) { + paths.push((path.to_owned(), method.to_owned(), cluster.to_owned())); return true; } } if empty { - self.tree - .domain_insert(hostname.into_bytes(), vec![(path, method, cluster_id)]); + self.tree.domain_insert( + hostname.into_bytes(), + vec![(path.to_owned(), method.to_owned(), cluster.to_owned())], + ); return true; } @@ -201,9 +201,9 @@ impl Router { pub fn remove_tree_rule( &mut self, hostname: &[u8], - path: PathRule, - method: MethodRule, - _cluster_id: Route, + path: &PathRule, + method: &MethodRule, + // _cluster: &Route, ) -> bool { let hostname = match from_utf8(hostname) { Err(_) => return false, @@ -216,7 +216,7 @@ impl Router { let paths_opt = self.tree.domain_lookup_mut(hostname.as_bytes(), false); if let Some((_, paths)) = paths_opt { - paths.retain(|(p, m, _)| *p != path || *m != method); + paths.retain(|(p, m, _)| p != path || m != method); } paths_opt @@ -237,17 +237,22 @@ impl Router { pub fn add_pre_rule( &mut self, - domain: DomainRule, - path: PathRule, - method: MethodRule, - cluster_id: Route, + domain: &DomainRule, + path: &PathRule, + method: &MethodRule, + cluster_id: &Route, ) -> bool { if !self .pre .iter() - .any(|(d, p, m, _)| *d == domain && *p == path && *m == method) + .any(|(d, p, m, _)| d == domain && p == path && m == method) { - self.pre.push((domain, path, method, cluster_id)); + self.pre.push(( + domain.to_owned(), + path.to_owned(), + method.to_owned(), + cluster_id.to_owned(), + )); true } else { false @@ -256,17 +261,22 @@ impl Router { pub fn add_post_rule( &mut self, - domain: DomainRule, - path: PathRule, - method: MethodRule, - cluster_id: Route, + domain: &DomainRule, + path: &PathRule, + method: &MethodRule, + cluster_id: &Route, ) -> bool { if !self .post .iter() - .any(|(d, p, m, _)| *d == domain && *p == path && *m == method) + .any(|(d, p, m, _)| d == domain && p == path && m == method) { - self.post.push((domain, path, method, cluster_id)); + self.post.push(( + domain.to_owned(), + path.to_owned(), + method.to_owned(), + cluster_id.to_owned(), + )); true } else { false @@ -275,14 +285,14 @@ impl Router { pub fn remove_pre_rule( &mut self, - domain: DomainRule, - path: PathRule, - method: MethodRule, + domain: &DomainRule, + path: &PathRule, + method: &MethodRule, ) -> bool { match self .pre .iter() - .position(|(d, p, m, _)| *d == domain && *p == path && *m == method) + .position(|(d, p, m, _)| d == domain && p == path && m == method) { None => false, Some(index) => { @@ -294,14 +304,14 @@ impl Router { pub fn remove_post_rule( &mut self, - domain: DomainRule, - path: PathRule, - method: MethodRule, + domain: &DomainRule, + path: &PathRule, + method: &MethodRule, ) -> bool { match self .post .iter() - .position(|(d, p, m, _)| *d == domain && *p == path && *m == method) + .position(|(d, p, m, _)| d == domain && p == path && m == method) { None => false, Some(index) => { @@ -650,28 +660,28 @@ mod tests { let mut router = Router::new(); assert!(router.add_pre_rule( - "*".parse::().unwrap(), - PathRule::Prefix("/.well-known/acme-challenge".to_string()), - MethodRule::new(Some("GET".to_string())), - Route::ClusterId("acme".to_string()) + &"*".parse::().unwrap(), + &PathRule::Prefix("/.well-known/acme-challenge".to_string()), + &MethodRule::new(Some("GET".to_string())), + &Route::ClusterId("acme".to_string()) )); assert!(router.add_tree_rule( - "www.example.com".as_bytes(), - PathRule::Prefix("/".to_string()), - MethodRule::new(Some("GET".to_string())), - Route::ClusterId("example".to_string()) + &"www.example.com".as_bytes(), + &PathRule::Prefix("/".to_string()), + &MethodRule::new(Some("GET".to_string())), + &Route::ClusterId("example".to_string()) )); assert!(router.add_tree_rule( - "*.test.example.com".as_bytes(), - PathRule::Regex(Regex::new("/hello[A-Z]+/").unwrap()), - MethodRule::new(Some("GET".to_string())), - Route::ClusterId("examplewildcard".to_string()) + &"*.test.example.com".as_bytes(), + &PathRule::Regex(Regex::new("/hello[A-Z]+/").unwrap()), + &MethodRule::new(Some("GET".to_string())), + &Route::ClusterId("examplewildcard".to_string()) )); assert!(router.add_tree_rule( - "/test[0-9]/.example.com".as_bytes(), - PathRule::Prefix("/".to_string()), - MethodRule::new(Some("GET".to_string())), - Route::ClusterId("exampleregex".to_string()) + &"/test[0-9]/.example.com".as_bytes(), + &PathRule::Prefix("/".to_string()), + &MethodRule::new(Some("GET".to_string())), + &Route::ClusterId("exampleregex".to_string()) )); assert_eq!(