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