Skip to main content

jokoway_rules/
lib.rs

1pub mod matcher;
2pub mod parser;
3pub mod prelude;
4pub mod registry;
5
6pub use matcher::Matcher;
7pub use parser::parse_rule;
8pub use registry::{parse_custom_rules, register_router_rule};
9
10#[cfg(test)]
11mod tests {
12    use super::parse_rule;
13    use crate::matcher::Matcher;
14    use crate::registry::register_router_rule;
15    use pingora::http::RequestHeader;
16    use winnow::ascii::multispace0;
17    use winnow::token::literal as tag;
18    use winnow::{Parser, Result};
19
20    #[test]
21    fn test_rule_parsing_and_matching() {
22        // Create a dummy request
23        let mut req = RequestHeader::build("GET", b"/api/v1/users", None).unwrap();
24        req.insert_header("Host", "example.com").unwrap();
25
26        // 1. Simple Host Match
27        let rule = parse_rule("Host(`example.com`)").unwrap();
28        assert!(rule.matches(&req));
29
30        let rule = parse_rule("Host(`other.com`)").unwrap();
31        assert!(!rule.matches(&req));
32
33        // 2. Path Prefix Match
34        let rule = parse_rule("PathPrefix(`/api`)").unwrap();
35        assert!(rule.matches(&req));
36
37        // 3. AND Logic
38        let rule = parse_rule("Host(`example.com`) && PathPrefix(`/api`)").unwrap();
39        assert!(rule.matches(&req));
40
41        // 4. OR Logic
42        let rule = parse_rule("Host(`other.com`) || PathPrefix(`/api`)").unwrap();
43        assert!(rule.matches(&req));
44
45        // 5. Nested Logic
46        let rule =
47            parse_rule("(Host(`example.com`) && Path(`/foo`)) || PathPrefix(`/api`)").unwrap();
48        assert!(rule.matches(&req));
49    }
50
51    #[test]
52    fn test_regex_and_not_matching() {
53        let mut req = RequestHeader::build("GET", b"/api/v1/users", None).unwrap();
54        req.insert_header("Host", "sub.example.com").unwrap();
55
56        // 1. HostRegexp
57        let rule = parse_rule("HostRegexp(`.*\\.example\\.com`)").unwrap();
58        assert!(rule.matches(&req));
59
60        let rule = parse_rule("HostRegexp(`^example\\.com$`)").unwrap();
61        assert!(!rule.matches(&req));
62
63        // 2. PathRegexp
64        let rule = parse_rule("PathRegexp(`^/api/v\\d+/.*`)").unwrap();
65        assert!(rule.matches(&req));
66
67        // 3. Not Matcher
68        let rule = parse_rule("!Host(`other.com`)").unwrap();
69        assert!(rule.matches(&req));
70
71        let rule = parse_rule("!Host(`sub.example.com`)").unwrap();
72        assert!(!rule.matches(&req));
73
74        // 4. Combined
75        let rule = parse_rule("HostRegexp(`.*\\.example\\.com`) && !PathPrefix(`/admin`)").unwrap();
76        assert!(rule.matches(&req));
77    }
78
79    #[derive(Debug)]
80    struct CustomMatcher;
81    impl Matcher for CustomMatcher {
82        fn matches(&self, _req: &RequestHeader) -> bool {
83            true
84        }
85
86        fn clone_box(&self) -> Box<dyn Matcher> {
87            Box::new(CustomMatcher)
88        }
89    }
90
91    #[test]
92    fn test_custom_rule_registry() {
93        // Define a custom parser
94        fn parse_my_rule(input: &mut &str) -> Result<Box<dyn Matcher>> {
95            (tag("MyRule"), multispace0, '(', ')')
96                .map(|_| Box::new(CustomMatcher) as Box<dyn Matcher>)
97                .parse_next(input)
98        }
99
100        // Register it
101        register_router_rule(parse_my_rule);
102
103        // Parse it
104        let rule = parse_rule("MyRule()").unwrap();
105
106        // Test it
107        let req = RequestHeader::build("GET", b"/", None).unwrap();
108        assert!(rule.matches(&req));
109    }
110
111    #[test]
112    fn test_header_and_query_regexp() {
113        // HeaderRegexp
114        let rule = parse_rule("HeaderRegexp(`User-Agent`, `^Mozilla.*`)").unwrap();
115        let mut req = RequestHeader::build("GET", b"/", None).unwrap();
116        req.insert_header(
117            "User-Agent",
118            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
119        )
120        .unwrap();
121        assert!(rule.matches(&req));
122
123        req.insert_header("User-Agent", "Curl/7.64.1").unwrap();
124        assert!(!rule.matches(&req));
125
126        // QueryRegexp
127        let rule = parse_rule("QueryRegexp(`id`, `^[0-9]+$`)").unwrap();
128        let req_match = RequestHeader::build("GET", b"/path?id=123", None).unwrap();
129        assert!(rule.matches(&req_match));
130
131        let req_no_match = RequestHeader::build("GET", b"/path?id=abc", None).unwrap();
132        assert!(!rule.matches(&req_no_match));
133    }
134}