casbin/model/
function_map.rs

1#[cfg(all(feature = "runtime-async-std", feature = "ip"))]
2use async_std::net::IpAddr;
3
4#[cfg(all(feature = "runtime-tokio", feature = "ip"))]
5use std::net::IpAddr;
6
7#[cfg(feature = "glob")]
8use globset::GlobBuilder;
9#[cfg(feature = "ip")]
10use ip_network::IpNetwork;
11use once_cell::sync::Lazy;
12use regex::Regex;
13use rhai::Dynamic;
14
15static MAT_B: Lazy<Regex> = Lazy::new(|| Regex::new(r":[^/]*").unwrap());
16static MAT_P: Lazy<Regex> = Lazy::new(|| Regex::new(r"\{[^/]*\}").unwrap());
17
18use std::{borrow::Cow, collections::HashMap};
19
20/// Represents a custom operator function that can be registered with Casbin.
21///
22/// Custom functions accept Rhai's `Dynamic` type, which can hold any value:
23/// - Strings (as `ImmutableString`)
24/// - Integers (i32 or i64)
25/// - Booleans
26/// - Floats (f32 or f64)
27/// - Arrays
28/// - Maps
29/// - And more...
30///
31/// This allows for flexible custom functions that can work with different types.
32///
33/// # Example
34///
35/// ```rust,ignore
36/// use casbin::{CoreApi, OperatorFunction};
37/// use rhai::Dynamic;
38///
39/// // Function that works with integers
40/// let int_fn = OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| {
41///     let a_int = a.as_int().unwrap_or(0);
42///     let b_int = b.as_int().unwrap_or(0);
43///     (a_int > b_int).into()
44/// });
45///
46/// // Function that works with strings
47/// let str_fn = OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| {
48///     use casbin::model::function_map::dynamic_to_str;
49///     let a_str = dynamic_to_str(&a);
50///     let b_str = dynamic_to_str(&b);
51///     a_str.contains(b_str.as_ref()).into()
52/// });
53/// ```
54#[derive(Clone, Copy)]
55pub enum OperatorFunction {
56    Arg0(fn() -> Dynamic),
57    Arg1(fn(Dynamic) -> Dynamic),
58    Arg2(fn(Dynamic, Dynamic) -> Dynamic),
59    Arg3(fn(Dynamic, Dynamic, Dynamic) -> Dynamic),
60    Arg4(fn(Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic),
61    Arg5(fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic),
62    Arg6(fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic),
63}
64
65pub struct FunctionMap {
66    pub(crate) fm: HashMap<String, OperatorFunction>,
67}
68
69impl Default for FunctionMap {
70    fn default() -> FunctionMap {
71        let mut fm: HashMap<String, OperatorFunction> = HashMap::new();
72        fm.insert(
73            "keyMatch".to_owned(),
74            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
75                key_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
76            }),
77        );
78        fm.insert(
79            "keyGet".to_owned(),
80            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
81                key_get(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
82            }),
83        );
84        fm.insert(
85            "keyMatch2".to_owned(),
86            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
87                key_match2(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
88            }),
89        );
90        fm.insert(
91            "keyGet2".to_owned(),
92            OperatorFunction::Arg3(|s1: Dynamic, s2: Dynamic, s3: Dynamic| {
93                key_get2(
94                    &dynamic_to_str(&s1),
95                    &dynamic_to_str(&s2),
96                    &dynamic_to_str(&s3),
97                )
98                .into()
99            }),
100        );
101        fm.insert(
102            "keyMatch3".to_owned(),
103            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
104                key_match3(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
105            }),
106        );
107        fm.insert(
108            "keyGet3".to_owned(),
109            OperatorFunction::Arg3(|s1: Dynamic, s2: Dynamic, s3: Dynamic| {
110                key_get3(
111                    &dynamic_to_str(&s1),
112                    &dynamic_to_str(&s2),
113                    &dynamic_to_str(&s3),
114                )
115                .into()
116            }),
117        );
118        fm.insert(
119            "keyMatch4".to_owned(),
120            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
121                key_match4(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
122            }),
123        );
124        fm.insert(
125            "keyMatch5".to_owned(),
126            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
127                key_match5(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
128            }),
129        );
130        fm.insert(
131            "regexMatch".to_owned(),
132            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
133                regex_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
134            }),
135        );
136
137        #[cfg(feature = "glob")]
138        fm.insert(
139            "globMatch".to_owned(),
140            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
141                glob_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
142            }),
143        );
144
145        #[cfg(feature = "ip")]
146        fm.insert(
147            "ipMatch".to_owned(),
148            OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
149                ip_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into()
150            }),
151        );
152
153        FunctionMap { fm }
154    }
155}
156
157impl FunctionMap {
158    #[inline]
159    pub fn add_function(&mut self, fname: &str, f: OperatorFunction) {
160        self.fm.insert(fname.to_owned(), f);
161    }
162
163    #[inline]
164    pub fn get_functions(
165        &self,
166    ) -> impl Iterator<Item = (&String, &OperatorFunction)> {
167        self.fm.iter()
168    }
169}
170
171/// Helper function to convert Dynamic to string reference
172///
173/// This is useful for custom functions that need string arguments.
174/// The function accepts Rhai's Dynamic type and converts it to a string.
175///
176/// # Example
177///
178/// ```rust,ignore
179/// use casbin::{CoreApi, OperatorFunction, Enforcer};
180/// use casbin::model::function_map::dynamic_to_str;
181/// use rhai::Dynamic;
182///
183/// // Create a custom function that takes Dynamic arguments
184/// let custom_fn = OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
185///     let str1 = dynamic_to_str(&s1);
186///     let str2 = dynamic_to_str(&s2);
187///     // Your custom logic here
188///     (str1 == str2).into()
189/// });
190/// ```
191pub fn dynamic_to_str(d: &Dynamic) -> Cow<'_, str> {
192    if d.is_string() {
193        match d.clone().into_immutable_string() {
194            Ok(s) => Cow::Owned(s.to_string()),
195            Err(_) => Cow::Owned(d.to_string()),
196        }
197    } else {
198        Cow::Owned(d.to_string())
199    }
200}
201
202/// key_match determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain *  
203/// For example, "/foo/bar" matches "/foo/*"
204pub fn key_match(key1: &str, key2: &str) -> bool {
205    if let Some(i) = key2.find('*') {
206        if key1.len() > i {
207            return key1[..i] == key2[..i];
208        }
209        key1[..] == key2[..i]
210    } else {
211        key1 == key2
212    }
213}
214
215/// key_get returns the matched part  
216/// For example, "/foo/bar/foo" matches "/foo/*"  
217/// "bar/foo" will be returned.
218pub fn key_get(key1: &str, key2: &str) -> String {
219    if let Some(i) = key2.find('*') {
220        if key1.len() > i && key1[..i] == key2[..i] {
221            return key1[i..].to_string();
222        }
223    }
224    "".to_string()
225}
226
227/// key_match2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *  
228/// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource"
229pub fn key_match2(key1: &str, key2: &str) -> bool {
230    let mut key2: Cow<str> = if key2.contains("/*") {
231        key2.replace("/*", "/.*").into()
232    } else {
233        key2.into()
234    };
235
236    key2 = MAT_B.replace_all(&key2, "[^/]+").to_string().into();
237
238    regex_match(key1, &format!("^{}$", key2))
239}
240
241/// key_get2 returns value matched pattern  
242/// For example, "/resource1" matches "/:resource"  
243/// if the pathVar == "resource", then "resource1" will be returned.
244pub fn key_get2(key1: &str, key2: &str, path_var: &str) -> String {
245    let key2: Cow<str> = if key2.contains("/*") {
246        key2.replace("/*", "/.*").into()
247    } else {
248        key2.into()
249    };
250
251    let re = Regex::new(r":[^/]+").unwrap();
252    let keys: Vec<_> = re.find_iter(&key2).collect();
253    let key2 = re.replace_all(&key2, "([^/]+)").to_string();
254    let key2 = format!("^{}$", key2);
255
256    if let Ok(re2) = Regex::new(&key2) {
257        if let Some(caps) = re2.captures(key1) {
258            for (i, key) in keys.iter().enumerate() {
259                if path_var == &key.as_str()[1..] {
260                    return caps
261                        .get(i + 1)
262                        .map_or("".to_string(), |m| m.as_str().to_string());
263                }
264            }
265        }
266    }
267    "".to_string()
268}
269
270/// key_match3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *  
271/// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}"
272pub fn key_match3(key1: &str, key2: &str) -> bool {
273    let mut key2: Cow<str> = if key2.contains("/*") {
274        key2.replace("/*", "/.*").into()
275    } else {
276        key2.into()
277    };
278
279    key2 = MAT_P.replace_all(&key2, "[^/]+").to_string().into();
280
281    regex_match(key1, &format!("^{}$", key2))
282}
283
284/// key_get3 returns value matched pattern  
285/// For example, "project/proj_project1_admin/" matches "project/proj_{project}_admin/"  
286/// if the pathVar == "project", then "project1" will be returned.
287pub fn key_get3(key1: &str, key2: &str, path_var: &str) -> String {
288    let key2: Cow<str> = if key2.contains("/*") {
289        key2.replace("/*", "/.*").into()
290    } else {
291        key2.into()
292    };
293
294    let re = Regex::new(r"\{[^/]+?\}").unwrap();
295    let keys: Vec<_> = re.find_iter(&key2).collect();
296    let key2 = re.replace_all(&key2, "([^/]+?)").to_string();
297    let key2 = Regex::new(r"\{")
298        .unwrap()
299        .replace_all(&key2, "\\{")
300        .to_string();
301    let key2 = format!("^{}$", key2);
302
303    let re2 = Regex::new(&key2).unwrap();
304    if let Some(caps) = re2.captures(key1) {
305        for (i, key) in keys.iter().enumerate() {
306            if path_var == &key.as_str()[1..key.as_str().len() - 1] {
307                return caps
308                    .get(i + 1)
309                    .map_or("".to_string(), |m| m.as_str().to_string());
310            }
311        }
312    }
313    "".to_string()
314}
315
316/// KeyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.  
317/// Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns:  
318///   - "/parent/123/child/123" matches "/parent/{id}/child/{id}"
319///   - "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
320///
321/// But KeyMatch3 will match both.
322pub fn key_match4(key1: &str, key2: &str) -> bool {
323    let mut key2 = key2.replace("/*", "/.*");
324    let mut tokens = Vec::new();
325
326    let re = Regex::new(r"\{[^/]+?\}").unwrap();
327    key2 = re
328        .replace_all(&key2, |caps: &regex::Captures| {
329            tokens.push(caps[0][1..caps[0].len() - 1].to_string());
330            "([^/]+)".to_string()
331        })
332        .to_string();
333
334    let re = match Regex::new(&format!("^{}$", key2)) {
335        Ok(re) => re,
336        Err(_) => return false,
337    };
338    if let Some(caps) = re.captures(key1) {
339        let matches: Vec<_> =
340            caps.iter().skip(1).map(|m| m.unwrap().as_str()).collect();
341        if tokens.len() != matches.len() {
342            panic!(
343                "KeyMatch4: number of tokens is not equal to number of values"
344            );
345        }
346
347        let mut values = HashMap::new();
348        for (token, value) in tokens.iter().zip(matches.iter()) {
349            if let Some(existing_value) = values.get(token) {
350                if *existing_value != value {
351                    return false;
352                }
353            } else {
354                values.insert(token, value);
355            }
356        }
357        true
358    } else {
359        false
360    }
361}
362
363/// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *  
364/// For example,  
365///   - "/foo/bar?status=1&type=2" matches "/foo/bar"
366///   - "/parent/child1" and "/parent/child1" matches "/parent/*"
367///   - "/parent/child1?status=1" matches "/parent/*".
368pub fn key_match5(key1: &str, key2: &str) -> bool {
369    let key1 = if let Some(i) = key1.find('?') {
370        &key1[..i]
371    } else {
372        key1
373    };
374
375    let key2 = key2.replace("/*", "/.*");
376    let key2 = Regex::new(r"(\{[^/]+?\})")
377        .unwrap()
378        .replace_all(&key2, "[^/]+");
379
380    regex_match(key1, &format!("^{}$", key2))
381}
382
383// regex_match determines whether key1 matches the pattern of key2 in regular expression.
384pub fn regex_match(key1: &str, key2: &str) -> bool {
385    Regex::new(key2).unwrap().is_match(key1)
386}
387
388// ip_match determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern.
389// For example, "192.168.2.123" matches "192.168.2.0/24"
390#[cfg(feature = "ip")]
391pub fn ip_match(key1: &str, key2: &str) -> bool {
392    let key2_split = key2.splitn(2, '/').collect::<Vec<&str>>();
393    let ip_addr2 = key2_split[0];
394
395    if let (Ok(ip_addr1), Ok(ip_addr2)) =
396        (key1.parse::<IpAddr>(), ip_addr2.parse::<IpAddr>())
397    {
398        if key2_split.len() == 2 {
399            match key2_split[1].parse::<u8>() {
400                Ok(ip_netmask) => {
401                    match IpNetwork::new_truncate(ip_addr2, ip_netmask) {
402                        Ok(ip_network) => ip_network.contains(ip_addr1),
403                        Err(err) => panic!("invalid ip network {}", err),
404                    }
405                }
406                _ => panic!("invalid netmask {}", key2_split[1]),
407            }
408        } else {
409            if let (IpAddr::V4(ip_addr1_new), IpAddr::V6(ip_addr2_new)) =
410                (ip_addr1, ip_addr2)
411            {
412                if let Some(ip_addr2_new) = ip_addr2_new.to_ipv4() {
413                    return ip_addr2_new == ip_addr1_new;
414                }
415            }
416
417            ip_addr1 == ip_addr2
418        }
419    } else {
420        panic!("invalid argument {} {}", key1, key2)
421    }
422}
423
424// glob_match determines whether key1 matches the pattern of key2 using glob pattern
425#[cfg(feature = "glob")]
426pub fn glob_match(key1: &str, key2: &str) -> bool {
427    GlobBuilder::new(key2)
428        .literal_separator(true)
429        .build()
430        .unwrap()
431        .compile_matcher()
432        .is_match(key1)
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[test]
440    fn test_key_match() {
441        assert!(key_match("/foo/bar", "/foo/*"));
442        assert!(!key_match("/bar/foo", "/foo/*"));
443        assert!(key_match("/bar", "/ba*"));
444    }
445
446    #[test]
447    fn test_key_get() {
448        assert_eq!(key_get("/foo", "/foo"), "");
449        assert_eq!(key_get("/foo", "/foo*"), "");
450        assert_eq!(key_get("/foo", "/foo/*"), "");
451        assert_eq!(key_get("/foo/bar", "/foo"), "");
452        assert_eq!(key_get("/foo/bar", "/foo*"), "/bar");
453        assert_eq!(key_get("/foo/bar", "/foo/*"), "bar");
454        assert_eq!(key_get("/foobar", "/foo"), "");
455        assert_eq!(key_get("/foobar", "/foo*"), "bar");
456        assert_eq!(key_get("/foobar", "/foo/*"), "");
457    }
458
459    #[test]
460    fn test_key_match2() {
461        assert!(key_match2("/foo/bar", "/foo/*"));
462        assert!(key_match2("/foo/bar/baz", "/foo/*"));
463        assert!(key_match2("/foo/baz", "/foo/:bar"));
464        assert!(key_match2("/foo/baz", "/:foo/:bar"));
465        assert!(key_match2("/foo/baz/foo", "/foo/:bar/foo"));
466        assert!(!key_match2("/baz", "/foo"));
467
468        // GH Issue #282
469        assert!(key_match2("/foo/bar", "/foo/:"));
470        assert!(!key_match2("/foo/bar/baz", "/foo/:"));
471        assert!(key_match2("/foo/bar/baz", "/foo/:/baz"));
472        assert!(!key_match2("/foo/bar", "/foo/:/baz"));
473    }
474
475    #[test]
476    fn test_key_get2() {
477        assert_eq!(key_get2("/foo", "/foo", "id"), "");
478        assert_eq!(key_get2("/foo", "/foo*", "id"), "");
479        assert_eq!(key_get2("/foo", "/foo/*", "id"), "");
480        assert_eq!(key_get2("/foo/bar", "/foo", "id"), "");
481        assert_eq!(key_get2("/foo/bar", "/foo*", "id"), "");
482        assert_eq!(key_get2("/foo/bar", "/foo/*", "id"), "");
483        assert_eq!(key_get2("/foobar", "/foo", "id"), "");
484        assert_eq!(key_get2("/foobar", "/foo*", "id"), "");
485        assert_eq!(key_get2("/foobar", "/foo/*", "id"), "");
486
487        assert_eq!(key_get2("/", "/:resource", "resource"), "");
488        assert_eq!(
489            key_get2("/resource1", "/:resource", "resource"),
490            "resource1"
491        );
492        assert_eq!(key_get2("/myid", "/:id/using/:resId", "id"), "");
493        assert_eq!(
494            key_get2("/myid/using/myresid", "/:id/using/:resId", "id"),
495            "myid"
496        );
497        assert_eq!(
498            key_get2("/myid/using/myresid", "/:id/using/:resId", "resId"),
499            "myresid"
500        );
501
502        assert_eq!(key_get2("/proxy/myid", "/proxy/:id/*", "id"), "");
503        assert_eq!(key_get2("/proxy/myid/", "/proxy/:id/*", "id"), "myid");
504        assert_eq!(key_get2("/proxy/myid/res", "/proxy/:id/*", "id"), "myid");
505        assert_eq!(
506            key_get2("/proxy/myid/res/res2", "/proxy/:id/*", "id"),
507            "myid"
508        );
509        assert_eq!(
510            key_get2("/proxy/myid/res/res2/res3", "/proxy/:id/*", "id"),
511            "myid"
512        );
513        assert_eq!(
514            key_get2("/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id"),
515            "myid"
516        );
517        assert_eq!(key_get2("/proxy/", "/proxy/:id/*", "id"), "");
518
519        assert_eq!(key_get2("/alice", "/:id", "id"), "alice");
520        assert_eq!(key_get2("/alice/all", "/:id/all", "id"), "alice");
521        assert_eq!(key_get2("/alice", "/:id/all", "id"), "");
522        assert_eq!(key_get2("/alice/all", "/:id", "id"), "");
523
524        assert_eq!(key_get2("/alice/all", "/:/all", ""), "");
525    }
526
527    #[test]
528    fn test_regex_match() {
529        assert!(regex_match("foobar", "^foo*"));
530        assert!(!regex_match("barfoo", "^foo*"));
531    }
532
533    #[test]
534    fn test_key_match3() {
535        assert!(key_match3("/foo/bar", "/foo/*"));
536        assert!(key_match3("/foo/bar/baz", "/foo/*"));
537        assert!(key_match3("/foo/baz", "/foo/{bar}"));
538        assert!(key_match3("/foo/baz/foo", "/foo/{bar}/foo"));
539        assert!(!key_match3("/baz", "/foo"));
540
541        // GH Issue #282
542        assert!(key_match3("/foo/bar", "/foo/{}"));
543        assert!(key_match3("/foo/{}", "/foo/{}"));
544        assert!(!key_match3("/foo/bar/baz", "/foo/{}"));
545        assert!(!key_match3("/foo/bar", "/foo/{}/baz"));
546        assert!(key_match3("/foo/bar/baz", "/foo/{}/baz"));
547    }
548
549    #[test]
550    fn test_key_get3() {
551        assert_eq!(key_get3("/foo", "/foo", "id"), "");
552        assert_eq!(key_get3("/foo", "/foo*", "id"), "");
553        assert_eq!(key_get3("/foo", "/foo/*", "id"), "");
554        assert_eq!(key_get3("/foo/bar", "/foo", "id"), "");
555        assert_eq!(key_get3("/foo/bar", "/foo*", "id"), "");
556        assert_eq!(key_get3("/foo/bar", "/foo/*", "id"), "");
557        assert_eq!(key_get3("/foobar", "/foo", "id"), "");
558        assert_eq!(key_get3("/foobar", "/foo*", "id"), "");
559        assert_eq!(key_get3("/foobar", "/foo/*", "id"), "");
560
561        assert_eq!(key_get3("/", "/{resource}", "resource"), "");
562        assert_eq!(
563            key_get3("/resource1", "/{resource}", "resource"),
564            "resource1"
565        );
566        assert_eq!(key_get3("/myid", "/{id}/using/{resId}", "id"), "");
567        assert_eq!(
568            key_get3("/myid/using/myresid", "/{id}/using/{resId}", "id"),
569            "myid"
570        );
571        assert_eq!(
572            key_get3("/myid/using/myresid", "/{id}/using/{resId}", "resId"),
573            "myresid"
574        );
575
576        assert_eq!(key_get3("/proxy/myid", "/proxy/{id}/*", "id"), "");
577        assert_eq!(key_get3("/proxy/myid/", "/proxy/{id}/*", "id"), "myid");
578        assert_eq!(key_get3("/proxy/myid/res", "/proxy/{id}/*", "id"), "myid");
579        assert_eq!(
580            key_get3("/proxy/myid/res/res2", "/proxy/{id}/*", "id"),
581            "myid"
582        );
583        assert_eq!(
584            key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id"),
585            "myid"
586        );
587        assert_eq!(
588            key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/res/*", "id"),
589            "myid"
590        );
591        assert_eq!(key_get3("/proxy/", "/proxy/{id}/*", "id"), "");
592
593        assert_eq!(
594            key_get3(
595                "/api/group1_group_name/project1_admin/info",
596                "/api/{proj}_admin/info",
597                "proj"
598            ),
599            ""
600        );
601        assert_eq!(
602            key_get3("/{id/using/myresid", "/{id/using/{resId}", "resId"),
603            "myresid"
604        );
605        assert_eq!(
606            key_get3(
607                "/{id/using/myresid/status}",
608                "/{id/using/{resId}/status}",
609                "resId"
610            ),
611            "myresid"
612        );
613
614        assert_eq!(
615            key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res"),
616            "res3"
617        );
618        assert_eq!(
619            key_get3(
620                "/api/project1_admin/info",
621                "/api/{proj}_admin/info",
622                "proj"
623            ),
624            "project1"
625        );
626        assert_eq!(
627            key_get3(
628                "/api/group1_group_name/project1_admin/info",
629                "/api/{g}_{gn}/{proj}_admin/info",
630                "g"
631            ),
632            "group1"
633        );
634        assert_eq!(
635            key_get3(
636                "/api/group1_group_name/project1_admin/info",
637                "/api/{g}_{gn}/{proj}_admin/info",
638                "gn"
639            ),
640            "group_name"
641        );
642        assert_eq!(
643            key_get3(
644                "/api/group1_group_name/project1_admin/info",
645                "/api/{g}_{gn}/{proj}_admin/info",
646                "proj"
647            ),
648            "project1"
649        );
650    }
651
652    #[test]
653    fn test_key_match4() {
654        assert!(key_match4(
655            "/parent/123/child/123",
656            "/parent/{id}/child/{id}"
657        ));
658        assert!(!key_match4(
659            "/parent/123/child/456",
660            "/parent/{id}/child/{id}"
661        ));
662        assert!(key_match4(
663            "/parent/123/child/123",
664            "/parent/{id}/child/{another_id}"
665        ));
666        assert!(key_match4(
667            "/parent/123/child/456",
668            "/parent/{id}/child/{another_id}"
669        ));
670        assert!(key_match4(
671            "/parent/123/child/123",
672            "/parent/{id}/child/{id}"
673        ));
674        assert!(!key_match4(
675            "/parent/123/child/456",
676            "/parent/{id}/child/{id}"
677        ));
678        assert!(key_match4(
679            "/parent/123/child/123",
680            "/parent/{id}/child/{another_id}"
681        ));
682        assert!(key_match4(
683            "/parent/123/child/123/book/123",
684            "/parent/{id}/child/{id}/book/{id}"
685        ));
686        assert!(!key_match4(
687            "/parent/123/child/123/book/456",
688            "/parent/{id}/child/{id}/book/{id}"
689        ));
690        assert!(!key_match4(
691            "/parent/123/child/456/book/123",
692            "/parent/{id}/child/{id}/book/{id}"
693        ));
694        assert!(!key_match4(
695            "/parent/123/child/456/book/",
696            "/parent/{id}/child/{id}/book/{id}"
697        ));
698        assert!(!key_match4(
699            "/parent/123/child/456",
700            "/parent/{id}/child/{id}/book/{id}"
701        ));
702        assert!(!key_match4(
703            "/parent/123/child/123",
704            "/parent/{i/d}/child/{i/d}"
705        ));
706    }
707
708    #[test]
709    fn test_key_match5() {
710        assert!(key_match5("/foo/bar?status=1&type=2", "/foo/bar"));
711        assert!(key_match5("/parent/child1", "/parent/*"));
712        assert!(key_match5("/parent/child1?status=1", "/parent/*"));
713        assert!(key_match5("/parent/child1?status=1", "/parent/child1"));
714    }
715
716    #[cfg(feature = "ip")]
717    #[test]
718    fn test_ip_match() {
719        assert!(ip_match("::1", "::0:1"));
720        assert!(ip_match("192.168.1.1", "192.168.1.1"));
721        assert!(ip_match("127.0.0.1", "::ffff:127.0.0.1"));
722        assert!(ip_match("192.168.2.123", "192.168.2.0/24"));
723        assert!(!ip_match("::1", "127.0.0.2"));
724        assert!(!ip_match("192.168.2.189", "192.168.1.134/26"));
725    }
726
727    #[cfg(feature = "ip")]
728    #[test]
729    #[should_panic]
730    fn test_ip_match_panic_1() {
731        assert!(ip_match("I am alice", "127.0.0.1"));
732    }
733
734    #[cfg(feature = "ip")]
735    #[test]
736    #[should_panic]
737    fn test_ip_match_panic_2() {
738        assert!(ip_match("127.0.0.1", "I am alice"));
739    }
740
741    #[cfg(feature = "glob")]
742    #[test]
743    fn test_glob_match() {
744        assert!(glob_match("/abc/123", "/abc/*"));
745        assert!(!glob_match("/abc/123/456", "/abc/*"));
746        assert!(glob_match("/abc/123/456", "/abc/**"));
747    }
748}