From b0d44198ba42ecc0888b2d53e019202b0cdf2d5a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 31 Dec 2019 14:53:30 +0600 Subject: [PATCH] Support named parameters for ResourceDef::resource_path() in form of ((&k, &v), ...) --- router/CHANGES.txt | 4 +- router/Cargo.toml | 7 ++- router/src/lib.rs | 123 +++++++++++++++++++++++++++++++++++++ router/src/resource.rs | 135 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 252 insertions(+), 17 deletions(-) diff --git a/router/CHANGES.txt b/router/CHANGES.txt index c9283566..1898c253 100644 --- a/router/CHANGES.txt +++ b/router/CHANGES.txt @@ -1,6 +1,8 @@ # Changes -## [0.2.4] - 2019-12-xx +## [0.3.0] - 2019-12-31 + +* Support named parameters for `ResourceDef::resource_path()` in form of `((&k, &v), ...)` ## [0.2.3] - 2019-12-25 diff --git a/router/Cargo.toml b/router/Cargo.toml index 1e5f4d54..19b5bd01 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-router" -version = "0.2.4" +version = "0.3.0" authors = ["Nikolay Kim "] description = "Path router" keywords = ["actix"] @@ -18,10 +18,11 @@ path = "src/lib.rs" default = ["http"] [dependencies] -regex = "1.3.1" -serde = "1.0.104" bytestring = "0.1.2" +either = "1.5.3" +regex = "1.3.1" log = "0.4.8" +serde = "1.0.104" http = { version="0.2.0", optional=true } [dev-dependencies] diff --git a/router/src/lib.rs b/router/src/lib.rs index 7e41c4c3..ab4d1658 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -4,6 +4,8 @@ mod path; mod resource; mod router; +use either::Either; + pub use self::de::PathDeserializer; pub use self::path::Path; pub use self::resource::ResourceDef; @@ -35,6 +37,127 @@ impl ResourcePath for bytestring::ByteString { } } +pub trait ResourceElements { + fn elements(self, for_each: F) -> Option + where + F: FnMut(Either<&str, (&str, &str)>) -> Option; +} + +impl<'a, T: AsRef> ResourceElements for &'a [T] { + fn elements(self, mut for_each: F) -> Option + where + F: FnMut(Either<&str, (&str, &str)>) -> Option, + { + for t in self { + if let Some(res) = for_each(Either::Left(t.as_ref())) { + return Some(res); + } + } + None + } +} + +impl<'a, U, I> ResourceElements for &'a U +where + &'a U: IntoIterator, + I: AsRef, +{ + fn elements(self, mut for_each: F) -> Option + where + F: FnMut(Either<&str, (&str, &str)>) -> Option, + { + for t in self.into_iter() { + if let Some(res) = for_each(Either::Left(t.as_ref())) { + return Some(res); + } + } + None + } +} + +impl ResourceElements for Vec +where + I: AsRef, +{ + fn elements(self, mut for_each: F) -> Option + where + F: FnMut(Either<&str, (&str, &str)>) -> Option, + { + for t in self.iter() { + if let Some(res) = for_each(Either::Left(t.as_ref())) { + return Some(res); + } + } + None + } +} + +impl<'a, K, V, S> ResourceElements for std::collections::HashMap +where + K: AsRef, + V: AsRef, + S: std::hash::BuildHasher, +{ + fn elements(self, mut for_each: F) -> Option + where + F: FnMut(Either<&str, (&str, &str)>) -> Option, + { + for t in self.iter() { + if let Some(res) = for_each(Either::Right((t.0.as_ref(), t.1.as_ref()))) { + return Some(res); + } + } + None + } +} + +#[rustfmt::skip] +mod _m { +use super::*; +// macro_rules! elements_tuple ({ $(($n:tt, $T:ident)),+} => { +// impl<$($T: AsRef,)+> ResourceElements for ($($T,)+) { +// fn elements(self, mut for_each: F_) -> Option +// where +// F_: FnMut(Either<&str, (&str, &str)>) -> Option, +// { +// $( +// if let Some(res) = for_each(Either::Left(self.$n.as_ref())) { +// return Some(res) +// } +// )+ +// None +// } +// } +// }); + +macro_rules! elements_2tuple ({ $(($n:tt, $V:ident)),+} => { + impl<'a, $($V: AsRef,)+> ResourceElements for ($((&'a str, $V),)+) { + fn elements(self, mut for_each: F_) -> Option + where + F_: FnMut(Either<&str, (&str, &str)>) -> Option, + { + $( + if let Some(res) = for_each(Either::Right((self.$n.0, self.$n.1.as_ref()))) { + return Some(res) + } + )+ + None + } + } +}); + +elements_2tuple!((0, A)); +elements_2tuple!((0, A), (1, B)); +elements_2tuple!((0, A), (1, B), (2, C)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +elements_2tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +} + /// Helper trait for type that could be converted to path pattern pub trait IntoPattern { /// Signle patter diff --git a/router/src/resource.rs b/router/src/resource.rs index 999bedfe..84e772da 100644 --- a/router/src/resource.rs +++ b/router/src/resource.rs @@ -1,10 +1,11 @@ use std::cmp::min; +use std::collections::HashMap; use std::hash::{Hash, Hasher}; use regex::{escape, Regex, RegexSet}; use crate::path::{Path, PathItem}; -use crate::{IntoPattern, Resource, ResourcePath}; +use crate::{IntoPattern, Resource, ResourceElements, ResourcePath}; const MAX_DYNAMIC_SEGMENTS: usize = 16; @@ -463,26 +464,66 @@ impl ResourceDef { } /// Build resource path from elements. Returns `true` on success. - pub fn resource_path(&self, path: &mut String, elements: &mut U) -> bool - where - U: Iterator, - I: AsRef, - { + pub fn resource_path(&self, path: &mut String, data: U) -> bool { match self.tp { PatternType::Prefix(ref p) => path.push_str(p), PatternType::Static(ref p) => path.push_str(p), PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = elements.next() { - path.push_str(val.as_ref()) + let mut iter = self.elements.iter(); + let mut map = HashMap::new(); + + let result = data.elements(|item| match item { + either::Either::Left(val) => loop { + if let Some(el) = iter.next() { + match *el { + PatternElement::Str(ref s) => path.push_str(s), + PatternElement::Var(_) => { + path.push_str(val.as_ref()); + return None; + } + } + } else { + return Some(false); + } + }, + either::Either::Right((name, val)) => { + map.insert(name.to_string(), val.to_string()); + None + } + }); + + if result.is_some() { + return true; + } else { + if map.is_empty() { + // push static sections + loop { + if let Some(el) = iter.next() { + match *el { + PatternElement::Str(ref s) => path.push_str(s), + PatternElement::Var(_) => { + return false; + } + } } else { - return false; + break; + } + } + } else { + for el in iter { + match *el { + PatternElement::Str(ref s) => path.push_str(s), + PatternElement::Var(ref name) => { + if let Some(val) = map.get(name) { + path.push_str(val); + continue; + } + return false; + } } } } + return true; } } PatternType::DynamicSet(..) => { @@ -632,6 +673,7 @@ pub(crate) fn insert_slash(path: &str) -> String { mod tests { use super::*; use http::Uri; + use std::collections::HashMap; use std::convert::TryFrom; #[test] @@ -858,4 +900,71 @@ mod tests { assert_eq!(&path["name"], "test2"); assert_eq!(&path[0], "test2"); } + + #[test] + fn test_resource_path() { + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/test"); + assert!(resource.resource_path(&mut s, &["user1"])); + assert_eq!(s, "/user/user1/test"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, (("item1", "user1"),))); + assert_eq!(s, "/user/user1/test"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}/test"); + assert!(resource.resource_path(&mut s, &["item", "item2"])); + assert_eq!(s, "/user/item/item2/test"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, (("item1", "item"), ("item2", "item2")))); + assert_eq!(s, "/user/item/item2/test"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}"); + assert!(resource.resource_path(&mut s, &["item", "item2"])); + assert_eq!(s, "/user/item/item2"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, (("item1", "item"), ("item2", "item2")))); + assert_eq!(s, "/user/item/item2"); + + let mut s = String::new(); + let resource = ResourceDef::new("/user/{item1}/{item2}/"); + assert!(resource.resource_path(&mut s, &["item", "item2"])); + assert_eq!(s, "/user/item/item2/"); + + let mut s = String::new(); + assert!(!resource.resource_path(&mut s, &["item"])); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, &["item", "item2"])); + assert_eq!(s, "/user/item/item2/"); + assert!(!resource.resource_path(&mut s, &["item"])); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, vec!["item", "item2"])); + assert_eq!(s, "/user/item/item2/"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, &vec!["item", "item2"])); + assert_eq!(s, "/user/item/item2/"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, &vec!["item", "item2"][..])); + assert_eq!(s, "/user/item/item2/"); + + let mut map = HashMap::new(); + map.insert("item1", "item"); + map.insert("item2", "item2"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, map)); + assert_eq!(s, "/user/item/item2/"); + + let mut s = String::new(); + assert!(resource.resource_path(&mut s, (("item1", "item"), ("item2", "item2")))); + assert_eq!(s, "/user/item/item2/"); + } }