| #[cfg(all(feature = "runtime-async-std", feature = "ip"))] |
| use async_std::net::IpAddr; |
| |
| #[cfg(all(feature = "runtime-tokio", feature = "ip"))] |
| use std::net::IpAddr; |
| |
| #[cfg(feature = "glob")] |
| use globset::GlobBuilder; |
| #[cfg(feature = "ip")] |
| use ip_network::IpNetwork; |
| use once_cell::sync::Lazy; |
| use regex::Regex; |
| use rhai::Dynamic; |
| |
| static MAT_B: Lazy<Regex> = Lazy::new(|| Regex::new(r":[^/]*").unwrap()); |
| static MAT_P: Lazy<Regex> = Lazy::new(|| Regex::new(r"\{[^/]*\}").unwrap()); |
| |
| use std::{borrow::Cow, collections::HashMap}; |
| |
| /// Represents a custom operator function that can be registered with Casbin. |
| /// |
| /// Custom functions accept Rhai's `Dynamic` type, which can hold any value: |
| /// - Strings (as `ImmutableString`) |
| /// - Integers (i32 or i64) |
| /// - Booleans |
| /// - Floats (f32 or f64) |
| /// - Arrays |
| /// - Maps |
| /// - And more... |
| /// |
| /// This allows for flexible custom functions that can work with different types. |
| /// |
| /// # Example |
| /// |
| /// ```rust,ignore |
| /// use casbin::{CoreApi, OperatorFunction}; |
| /// use rhai::Dynamic; |
| /// |
| /// // Function that works with integers |
| /// let int_fn = OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { |
| /// let a_int = a.as_int().unwrap_or(0); |
| /// let b_int = b.as_int().unwrap_or(0); |
| /// (a_int > b_int).into() |
| /// }); |
| /// |
| /// // Function that works with strings |
| /// let str_fn = OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { |
| /// use casbin::model::function_map::dynamic_to_str; |
| /// let a_str = dynamic_to_str(&a); |
| /// let b_str = dynamic_to_str(&b); |
| /// a_str.contains(b_str.as_ref()).into() |
| /// }); |
| /// ``` |
| #[derive(Clone, Copy)] |
| pub enum OperatorFunction { |
| Arg0(fn() -> Dynamic), |
| Arg1(fn(Dynamic) -> Dynamic), |
| Arg2(fn(Dynamic, Dynamic) -> Dynamic), |
| Arg3(fn(Dynamic, Dynamic, Dynamic) -> Dynamic), |
| Arg4(fn(Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic), |
| Arg5(fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic), |
| Arg6(fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic), |
| } |
| |
| pub struct FunctionMap { |
| pub(crate) fm: HashMap<String, OperatorFunction>, |
| } |
| |
| impl Default for FunctionMap { |
| fn default() -> FunctionMap { |
| let mut fm: HashMap<String, OperatorFunction> = HashMap::new(); |
| fm.insert( |
| "keyMatch".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| key_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| fm.insert( |
| "keyGet".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| key_get(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| fm.insert( |
| "keyMatch2".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| key_match2(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| fm.insert( |
| "keyGet2".to_owned(), |
| OperatorFunction::Arg3(|s1: Dynamic, s2: Dynamic, s3: Dynamic| { |
| key_get2( |
| &dynamic_to_str(&s1), |
| &dynamic_to_str(&s2), |
| &dynamic_to_str(&s3), |
| ) |
| .into() |
| }), |
| ); |
| fm.insert( |
| "keyMatch3".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| key_match3(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| fm.insert( |
| "keyGet3".to_owned(), |
| OperatorFunction::Arg3(|s1: Dynamic, s2: Dynamic, s3: Dynamic| { |
| key_get3( |
| &dynamic_to_str(&s1), |
| &dynamic_to_str(&s2), |
| &dynamic_to_str(&s3), |
| ) |
| .into() |
| }), |
| ); |
| fm.insert( |
| "keyMatch4".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| key_match4(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| fm.insert( |
| "keyMatch5".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| key_match5(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| fm.insert( |
| "regexMatch".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| regex_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| |
| #[cfg(feature = "glob")] |
| fm.insert( |
| "globMatch".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| glob_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| |
| #[cfg(feature = "ip")] |
| fm.insert( |
| "ipMatch".to_owned(), |
| OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| ip_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() |
| }), |
| ); |
| |
| FunctionMap { fm } |
| } |
| } |
| |
| impl FunctionMap { |
| #[inline] |
| pub fn add_function(&mut self, fname: &str, f: OperatorFunction) { |
| self.fm.insert(fname.to_owned(), f); |
| } |
| |
| #[inline] |
| pub fn get_functions( |
| &self, |
| ) -> impl Iterator<Item = (&String, &OperatorFunction)> { |
| self.fm.iter() |
| } |
| } |
| |
| /// Helper function to convert Dynamic to string reference |
| /// |
| /// This is useful for custom functions that need string arguments. |
| /// The function accepts Rhai's Dynamic type and converts it to a string. |
| /// |
| /// # Example |
| /// |
| /// ```rust,ignore |
| /// use casbin::{CoreApi, OperatorFunction, Enforcer}; |
| /// use casbin::model::function_map::dynamic_to_str; |
| /// use rhai::Dynamic; |
| /// |
| /// // Create a custom function that takes Dynamic arguments |
| /// let custom_fn = OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { |
| /// let str1 = dynamic_to_str(&s1); |
| /// let str2 = dynamic_to_str(&s2); |
| /// // Your custom logic here |
| /// (str1 == str2).into() |
| /// }); |
| /// ``` |
| pub fn dynamic_to_str(d: &Dynamic) -> Cow<'_, str> { |
| if d.is_string() { |
| match d.clone().into_immutable_string() { |
| Ok(s) => Cow::Owned(s.to_string()), |
| Err(_) => Cow::Owned(d.to_string()), |
| } |
| } else { |
| Cow::Owned(d.to_string()) |
| } |
| } |
| |
| /// key_match determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain * |
| /// For example, "/foo/bar" matches "/foo/*" |
| pub fn key_match(key1: &str, key2: &str) -> bool { |
| if let Some(i) = key2.find('*') { |
| if key1.len() > i { |
| return key1[..i] == key2[..i]; |
| } |
| key1[..] == key2[..i] |
| } else { |
| key1 == key2 |
| } |
| } |
| |
| /// key_get returns the matched part |
| /// For example, "/foo/bar/foo" matches "/foo/*" |
| /// "bar/foo" will be returned. |
| pub fn key_get(key1: &str, key2: &str) -> String { |
| if let Some(i) = key2.find('*') { |
| if key1.len() > i && key1[..i] == key2[..i] { |
| return key1[i..].to_string(); |
| } |
| } |
| "".to_string() |
| } |
| |
| /// key_match2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a * |
| /// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource" |
| pub fn key_match2(key1: &str, key2: &str) -> bool { |
| let mut key2: Cow<str> = if key2.contains("/*") { |
| key2.replace("/*", "/.*").into() |
| } else { |
| key2.into() |
| }; |
| |
| key2 = MAT_B.replace_all(&key2, "[^/]+").to_string().into(); |
| |
| regex_match(key1, &format!("^{}$", key2)) |
| } |
| |
| /// key_get2 returns value matched pattern |
| /// For example, "/resource1" matches "/:resource" |
| /// if the pathVar == "resource", then "resource1" will be returned. |
| pub fn key_get2(key1: &str, key2: &str, path_var: &str) -> String { |
| let key2: Cow<str> = if key2.contains("/*") { |
| key2.replace("/*", "/.*").into() |
| } else { |
| key2.into() |
| }; |
| |
| let re = Regex::new(r":[^/]+").unwrap(); |
| let keys: Vec<_> = re.find_iter(&key2).collect(); |
| let key2 = re.replace_all(&key2, "([^/]+)").to_string(); |
| let key2 = format!("^{}$", key2); |
| |
| if let Ok(re2) = Regex::new(&key2) { |
| if let Some(caps) = re2.captures(key1) { |
| for (i, key) in keys.iter().enumerate() { |
| if path_var == &key.as_str()[1..] { |
| return caps |
| .get(i + 1) |
| .map_or("".to_string(), |m| m.as_str().to_string()); |
| } |
| } |
| } |
| } |
| "".to_string() |
| } |
| |
| /// key_match3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a * |
| /// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}" |
| pub fn key_match3(key1: &str, key2: &str) -> bool { |
| let mut key2: Cow<str> = if key2.contains("/*") { |
| key2.replace("/*", "/.*").into() |
| } else { |
| key2.into() |
| }; |
| |
| key2 = MAT_P.replace_all(&key2, "[^/]+").to_string().into(); |
| |
| regex_match(key1, &format!("^{}$", key2)) |
| } |
| |
| /// key_get3 returns value matched pattern |
| /// For example, "project/proj_project1_admin/" matches "project/proj_{project}_admin/" |
| /// if the pathVar == "project", then "project1" will be returned. |
| pub fn key_get3(key1: &str, key2: &str, path_var: &str) -> String { |
| let key2: Cow<str> = if key2.contains("/*") { |
| key2.replace("/*", "/.*").into() |
| } else { |
| key2.into() |
| }; |
| |
| let re = Regex::new(r"\{[^/]+?\}").unwrap(); |
| let keys: Vec<_> = re.find_iter(&key2).collect(); |
| let key2 = re.replace_all(&key2, "([^/]+?)").to_string(); |
| let key2 = Regex::new(r"\{") |
| .unwrap() |
| .replace_all(&key2, "\\{") |
| .to_string(); |
| let key2 = format!("^{}$", key2); |
| |
| let re2 = Regex::new(&key2).unwrap(); |
| if let Some(caps) = re2.captures(key1) { |
| for (i, key) in keys.iter().enumerate() { |
| if path_var == &key.as_str()[1..key.as_str().len() - 1] { |
| return caps |
| .get(i + 1) |
| .map_or("".to_string(), |m| m.as_str().to_string()); |
| } |
| } |
| } |
| "".to_string() |
| } |
| |
| /// KeyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. |
| /// Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns: |
| /// - "/parent/123/child/123" matches "/parent/{id}/child/{id}" |
| /// - "/parent/123/child/456" does not match "/parent/{id}/child/{id}" |
| /// |
| /// But KeyMatch3 will match both. |
| pub fn key_match4(key1: &str, key2: &str) -> bool { |
| let mut key2 = key2.replace("/*", "/.*"); |
| let mut tokens = Vec::new(); |
| |
| let re = Regex::new(r"\{[^/]+?\}").unwrap(); |
| key2 = re |
| .replace_all(&key2, |caps: ®ex::Captures| { |
| tokens.push(caps[0][1..caps[0].len() - 1].to_string()); |
| "([^/]+)".to_string() |
| }) |
| .to_string(); |
| |
| let re = match Regex::new(&format!("^{}$", key2)) { |
| Ok(re) => re, |
| Err(_) => return false, |
| }; |
| if let Some(caps) = re.captures(key1) { |
| let matches: Vec<_> = |
| caps.iter().skip(1).map(|m| m.unwrap().as_str()).collect(); |
| if tokens.len() != matches.len() { |
| panic!( |
| "KeyMatch4: number of tokens is not equal to number of values" |
| ); |
| } |
| |
| let mut values = HashMap::new(); |
| for (token, value) in tokens.iter().zip(matches.iter()) { |
| if let Some(existing_value) = values.get(token) { |
| if *existing_value != value { |
| return false; |
| } |
| } else { |
| values.insert(token, value); |
| } |
| } |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a * |
| /// For example, |
| /// - "/foo/bar?status=1&type=2" matches "/foo/bar" |
| /// - "/parent/child1" and "/parent/child1" matches "/parent/*" |
| /// - "/parent/child1?status=1" matches "/parent/*". |
| pub fn key_match5(key1: &str, key2: &str) -> bool { |
| let key1 = if let Some(i) = key1.find('?') { |
| &key1[..i] |
| } else { |
| key1 |
| }; |
| |
| let key2 = key2.replace("/*", "/.*"); |
| let key2 = Regex::new(r"(\{[^/]+?\})") |
| .unwrap() |
| .replace_all(&key2, "[^/]+"); |
| |
| regex_match(key1, &format!("^{}$", key2)) |
| } |
| |
| // regex_match determines whether key1 matches the pattern of key2 in regular expression. |
| pub fn regex_match(key1: &str, key2: &str) -> bool { |
| Regex::new(key2).unwrap().is_match(key1) |
| } |
| |
| // ip_match determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern. |
| // For example, "192.168.2.123" matches "192.168.2.0/24" |
| #[cfg(feature = "ip")] |
| pub fn ip_match(key1: &str, key2: &str) -> bool { |
| let key2_split = key2.splitn(2, '/').collect::<Vec<&str>>(); |
| let ip_addr2 = key2_split[0]; |
| |
| if let (Ok(ip_addr1), Ok(ip_addr2)) = |
| (key1.parse::<IpAddr>(), ip_addr2.parse::<IpAddr>()) |
| { |
| if key2_split.len() == 2 { |
| match key2_split[1].parse::<u8>() { |
| Ok(ip_netmask) => { |
| match IpNetwork::new_truncate(ip_addr2, ip_netmask) { |
| Ok(ip_network) => ip_network.contains(ip_addr1), |
| Err(err) => panic!("invalid ip network {}", err), |
| } |
| } |
| _ => panic!("invalid netmask {}", key2_split[1]), |
| } |
| } else { |
| if let (IpAddr::V4(ip_addr1_new), IpAddr::V6(ip_addr2_new)) = |
| (ip_addr1, ip_addr2) |
| { |
| if let Some(ip_addr2_new) = ip_addr2_new.to_ipv4() { |
| return ip_addr2_new == ip_addr1_new; |
| } |
| } |
| |
| ip_addr1 == ip_addr2 |
| } |
| } else { |
| panic!("invalid argument {} {}", key1, key2) |
| } |
| } |
| |
| // glob_match determines whether key1 matches the pattern of key2 using glob pattern |
| #[cfg(feature = "glob")] |
| pub fn glob_match(key1: &str, key2: &str) -> bool { |
| GlobBuilder::new(key2) |
| .literal_separator(true) |
| .build() |
| .unwrap() |
| .compile_matcher() |
| .is_match(key1) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_key_match() { |
| assert!(key_match("/foo/bar", "/foo/*")); |
| assert!(!key_match("/bar/foo", "/foo/*")); |
| assert!(key_match("/bar", "/ba*")); |
| } |
| |
| #[test] |
| fn test_key_get() { |
| assert_eq!(key_get("/foo", "/foo"), ""); |
| assert_eq!(key_get("/foo", "/foo*"), ""); |
| assert_eq!(key_get("/foo", "/foo/*"), ""); |
| assert_eq!(key_get("/foo/bar", "/foo"), ""); |
| assert_eq!(key_get("/foo/bar", "/foo*"), "/bar"); |
| assert_eq!(key_get("/foo/bar", "/foo/*"), "bar"); |
| assert_eq!(key_get("/foobar", "/foo"), ""); |
| assert_eq!(key_get("/foobar", "/foo*"), "bar"); |
| assert_eq!(key_get("/foobar", "/foo/*"), ""); |
| } |
| |
| #[test] |
| fn test_key_match2() { |
| assert!(key_match2("/foo/bar", "/foo/*")); |
| assert!(key_match2("/foo/bar/baz", "/foo/*")); |
| assert!(key_match2("/foo/baz", "/foo/:bar")); |
| assert!(key_match2("/foo/baz", "/:foo/:bar")); |
| assert!(key_match2("/foo/baz/foo", "/foo/:bar/foo")); |
| assert!(!key_match2("/baz", "/foo")); |
| |
| // GH Issue #282 |
| assert!(key_match2("/foo/bar", "/foo/:")); |
| assert!(!key_match2("/foo/bar/baz", "/foo/:")); |
| assert!(key_match2("/foo/bar/baz", "/foo/:/baz")); |
| assert!(!key_match2("/foo/bar", "/foo/:/baz")); |
| } |
| |
| #[test] |
| fn test_key_get2() { |
| assert_eq!(key_get2("/foo", "/foo", "id"), ""); |
| assert_eq!(key_get2("/foo", "/foo*", "id"), ""); |
| assert_eq!(key_get2("/foo", "/foo/*", "id"), ""); |
| assert_eq!(key_get2("/foo/bar", "/foo", "id"), ""); |
| assert_eq!(key_get2("/foo/bar", "/foo*", "id"), ""); |
| assert_eq!(key_get2("/foo/bar", "/foo/*", "id"), ""); |
| assert_eq!(key_get2("/foobar", "/foo", "id"), ""); |
| assert_eq!(key_get2("/foobar", "/foo*", "id"), ""); |
| assert_eq!(key_get2("/foobar", "/foo/*", "id"), ""); |
| |
| assert_eq!(key_get2("/", "/:resource", "resource"), ""); |
| assert_eq!( |
| key_get2("/resource1", "/:resource", "resource"), |
| "resource1" |
| ); |
| assert_eq!(key_get2("/myid", "/:id/using/:resId", "id"), ""); |
| assert_eq!( |
| key_get2("/myid/using/myresid", "/:id/using/:resId", "id"), |
| "myid" |
| ); |
| assert_eq!( |
| key_get2("/myid/using/myresid", "/:id/using/:resId", "resId"), |
| "myresid" |
| ); |
| |
| assert_eq!(key_get2("/proxy/myid", "/proxy/:id/*", "id"), ""); |
| assert_eq!(key_get2("/proxy/myid/", "/proxy/:id/*", "id"), "myid"); |
| assert_eq!(key_get2("/proxy/myid/res", "/proxy/:id/*", "id"), "myid"); |
| assert_eq!( |
| key_get2("/proxy/myid/res/res2", "/proxy/:id/*", "id"), |
| "myid" |
| ); |
| assert_eq!( |
| key_get2("/proxy/myid/res/res2/res3", "/proxy/:id/*", "id"), |
| "myid" |
| ); |
| assert_eq!( |
| key_get2("/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id"), |
| "myid" |
| ); |
| assert_eq!(key_get2("/proxy/", "/proxy/:id/*", "id"), ""); |
| |
| assert_eq!(key_get2("/alice", "/:id", "id"), "alice"); |
| assert_eq!(key_get2("/alice/all", "/:id/all", "id"), "alice"); |
| assert_eq!(key_get2("/alice", "/:id/all", "id"), ""); |
| assert_eq!(key_get2("/alice/all", "/:id", "id"), ""); |
| |
| assert_eq!(key_get2("/alice/all", "/:/all", ""), ""); |
| } |
| |
| #[test] |
| fn test_regex_match() { |
| assert!(regex_match("foobar", "^foo*")); |
| assert!(!regex_match("barfoo", "^foo*")); |
| } |
| |
| #[test] |
| fn test_key_match3() { |
| assert!(key_match3("/foo/bar", "/foo/*")); |
| assert!(key_match3("/foo/bar/baz", "/foo/*")); |
| assert!(key_match3("/foo/baz", "/foo/{bar}")); |
| assert!(key_match3("/foo/baz/foo", "/foo/{bar}/foo")); |
| assert!(!key_match3("/baz", "/foo")); |
| |
| // GH Issue #282 |
| assert!(key_match3("/foo/bar", "/foo/{}")); |
| assert!(key_match3("/foo/{}", "/foo/{}")); |
| assert!(!key_match3("/foo/bar/baz", "/foo/{}")); |
| assert!(!key_match3("/foo/bar", "/foo/{}/baz")); |
| assert!(key_match3("/foo/bar/baz", "/foo/{}/baz")); |
| } |
| |
| #[test] |
| fn test_key_get3() { |
| assert_eq!(key_get3("/foo", "/foo", "id"), ""); |
| assert_eq!(key_get3("/foo", "/foo*", "id"), ""); |
| assert_eq!(key_get3("/foo", "/foo/*", "id"), ""); |
| assert_eq!(key_get3("/foo/bar", "/foo", "id"), ""); |
| assert_eq!(key_get3("/foo/bar", "/foo*", "id"), ""); |
| assert_eq!(key_get3("/foo/bar", "/foo/*", "id"), ""); |
| assert_eq!(key_get3("/foobar", "/foo", "id"), ""); |
| assert_eq!(key_get3("/foobar", "/foo*", "id"), ""); |
| assert_eq!(key_get3("/foobar", "/foo/*", "id"), ""); |
| |
| assert_eq!(key_get3("/", "/{resource}", "resource"), ""); |
| assert_eq!( |
| key_get3("/resource1", "/{resource}", "resource"), |
| "resource1" |
| ); |
| assert_eq!(key_get3("/myid", "/{id}/using/{resId}", "id"), ""); |
| assert_eq!( |
| key_get3("/myid/using/myresid", "/{id}/using/{resId}", "id"), |
| "myid" |
| ); |
| assert_eq!( |
| key_get3("/myid/using/myresid", "/{id}/using/{resId}", "resId"), |
| "myresid" |
| ); |
| |
| assert_eq!(key_get3("/proxy/myid", "/proxy/{id}/*", "id"), ""); |
| assert_eq!(key_get3("/proxy/myid/", "/proxy/{id}/*", "id"), "myid"); |
| assert_eq!(key_get3("/proxy/myid/res", "/proxy/{id}/*", "id"), "myid"); |
| assert_eq!( |
| key_get3("/proxy/myid/res/res2", "/proxy/{id}/*", "id"), |
| "myid" |
| ); |
| assert_eq!( |
| key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id"), |
| "myid" |
| ); |
| assert_eq!( |
| key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/res/*", "id"), |
| "myid" |
| ); |
| assert_eq!(key_get3("/proxy/", "/proxy/{id}/*", "id"), ""); |
| |
| assert_eq!( |
| key_get3( |
| "/api/group1_group_name/project1_admin/info", |
| "/api/{proj}_admin/info", |
| "proj" |
| ), |
| "" |
| ); |
| assert_eq!( |
| key_get3("/{id/using/myresid", "/{id/using/{resId}", "resId"), |
| "myresid" |
| ); |
| assert_eq!( |
| key_get3( |
| "/{id/using/myresid/status}", |
| "/{id/using/{resId}/status}", |
| "resId" |
| ), |
| "myresid" |
| ); |
| |
| assert_eq!( |
| key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res"), |
| "res3" |
| ); |
| assert_eq!( |
| key_get3( |
| "/api/project1_admin/info", |
| "/api/{proj}_admin/info", |
| "proj" |
| ), |
| "project1" |
| ); |
| assert_eq!( |
| key_get3( |
| "/api/group1_group_name/project1_admin/info", |
| "/api/{g}_{gn}/{proj}_admin/info", |
| "g" |
| ), |
| "group1" |
| ); |
| assert_eq!( |
| key_get3( |
| "/api/group1_group_name/project1_admin/info", |
| "/api/{g}_{gn}/{proj}_admin/info", |
| "gn" |
| ), |
| "group_name" |
| ); |
| assert_eq!( |
| key_get3( |
| "/api/group1_group_name/project1_admin/info", |
| "/api/{g}_{gn}/{proj}_admin/info", |
| "proj" |
| ), |
| "project1" |
| ); |
| } |
| |
| #[test] |
| fn test_key_match4() { |
| assert!(key_match4( |
| "/parent/123/child/123", |
| "/parent/{id}/child/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/456", |
| "/parent/{id}/child/{id}" |
| )); |
| assert!(key_match4( |
| "/parent/123/child/123", |
| "/parent/{id}/child/{another_id}" |
| )); |
| assert!(key_match4( |
| "/parent/123/child/456", |
| "/parent/{id}/child/{another_id}" |
| )); |
| assert!(key_match4( |
| "/parent/123/child/123", |
| "/parent/{id}/child/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/456", |
| "/parent/{id}/child/{id}" |
| )); |
| assert!(key_match4( |
| "/parent/123/child/123", |
| "/parent/{id}/child/{another_id}" |
| )); |
| assert!(key_match4( |
| "/parent/123/child/123/book/123", |
| "/parent/{id}/child/{id}/book/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/123/book/456", |
| "/parent/{id}/child/{id}/book/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/456/book/123", |
| "/parent/{id}/child/{id}/book/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/456/book/", |
| "/parent/{id}/child/{id}/book/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/456", |
| "/parent/{id}/child/{id}/book/{id}" |
| )); |
| assert!(!key_match4( |
| "/parent/123/child/123", |
| "/parent/{i/d}/child/{i/d}" |
| )); |
| } |
| |
| #[test] |
| fn test_key_match5() { |
| assert!(key_match5("/foo/bar?status=1&type=2", "/foo/bar")); |
| assert!(key_match5("/parent/child1", "/parent/*")); |
| assert!(key_match5("/parent/child1?status=1", "/parent/*")); |
| assert!(key_match5("/parent/child1?status=1", "/parent/child1")); |
| } |
| |
| #[cfg(feature = "ip")] |
| #[test] |
| fn test_ip_match() { |
| assert!(ip_match("::1", "::0:1")); |
| assert!(ip_match("192.168.1.1", "192.168.1.1")); |
| assert!(ip_match("127.0.0.1", "::ffff:127.0.0.1")); |
| assert!(ip_match("192.168.2.123", "192.168.2.0/24")); |
| assert!(!ip_match("::1", "127.0.0.2")); |
| assert!(!ip_match("192.168.2.189", "192.168.1.134/26")); |
| } |
| |
| #[cfg(feature = "ip")] |
| #[test] |
| #[should_panic] |
| fn test_ip_match_panic_1() { |
| assert!(ip_match("I am alice", "127.0.0.1")); |
| } |
| |
| #[cfg(feature = "ip")] |
| #[test] |
| #[should_panic] |
| fn test_ip_match_panic_2() { |
| assert!(ip_match("127.0.0.1", "I am alice")); |
| } |
| |
| #[cfg(feature = "glob")] |
| #[test] |
| fn test_glob_match() { |
| assert!(glob_match("/abc/123", "/abc/*")); |
| assert!(!glob_match("/abc/123/456", "/abc/*")); |
| assert!(glob_match("/abc/123/456", "/abc/**")); |
| } |
| } |