Skip to main content

relay_cabi/
pattern.rs

1//! Relay pattern matching for the C-ABI.
2
3use relay_pattern::Pattern;
4
5use crate::RelayStr;
6
7/// A Relay pattern.
8pub struct RelayPattern;
9
10/// Represents a collection of compiled Relay patterns with shared options.
11pub struct RelayPatterns;
12
13/// Creates a new Relay [`Pattern`].
14#[unsafe(no_mangle)]
15#[relay_ffi::catch_unwind]
16pub unsafe extern "C" fn relay_pattern_new(
17    pattern: *const RelayStr,
18    case_insensitive: bool,
19    max_complexity: u64,
20) -> *mut RelayPattern {
21    let pattern = Pattern::builder(unsafe { (*pattern).as_str() })
22        .case_insensitive(case_insensitive)
23        .max_complexity(max_complexity)
24        .build()?;
25    Box::into_raw(Box::new(pattern)) as *mut RelayPattern
26}
27
28/// Returns `true` if the pattern matches the UTF-8 string.
29#[unsafe(no_mangle)]
30#[relay_ffi::catch_unwind]
31pub unsafe extern "C" fn relay_pattern_is_match(
32    pattern: *const RelayPattern,
33    value: *const RelayStr,
34) -> bool {
35    let pattern = unsafe { &*(pattern as *const Pattern) };
36    pattern.is_match(unsafe { (*value).as_str() })
37}
38
39/// Formats the pattern using its `Display` implementation.
40///
41/// The returned string is newly allocated and must be freed with `relay_str_free`.
42#[unsafe(no_mangle)]
43#[relay_ffi::catch_unwind]
44pub unsafe extern "C" fn relay_pattern_to_string(pattern: *const RelayPattern) -> RelayStr {
45    let pattern = unsafe { &*(pattern as *const Pattern) };
46    RelayStr::from_string(pattern.to_string())
47}
48
49/// Frees a compiled Relay pattern.
50#[unsafe(no_mangle)]
51#[relay_ffi::catch_unwind]
52pub unsafe extern "C" fn relay_pattern_free(pattern: *mut RelayPattern) {
53    if !pattern.is_null() {
54        drop(unsafe { Box::from_raw(pattern as *mut Pattern) });
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use crate::{
61        RelayErrorCode, relay_err_clear, relay_err_get_last_code, relay_err_get_last_message,
62    };
63
64    use super::*;
65
66    macro_rules! test_pattern {
67        ($pattern:expr, $haystack:expr, $is_match:expr) => {{
68            test_pattern!($pattern, $haystack, $is_match, i:false)
69        }};
70        ($pattern:expr, $haystack:expr, $is_match:expr, i:$case_insensitive:expr) => {{
71            let pattern = unsafe { relay_pattern_new(&RelayStr::new($pattern), $case_insensitive, u64::MAX) };
72            // On panic this leaks memory, but we're in a test and accept that.
73            assert!(!pattern.is_null());
74            assert_eq!(
75                unsafe { relay_pattern_is_match(pattern, &RelayStr::new($haystack)) },
76                $is_match,
77            );
78            unsafe { relay_pattern_free(pattern) };
79        }};
80    }
81
82    #[test]
83    fn test_pattern_case_sensitive() {
84        test_pattern!("*.{js,py}", "src/hello.py", true);
85        test_pattern!("*.{js,py}", "src/hello.rs", false);
86        test_pattern!("*.py", "hello.py.bak", false);
87        test_pattern!("h?llo", "héllo", true);
88        test_pattern!("[a-z]*", "hello", true);
89        test_pattern!("[a-z]*", "123", false);
90        test_pattern!("*", "hello\nworld", true);
91        test_pattern!("", "", false);
92        test_pattern!("*", "", true);
93    }
94
95    #[test]
96    fn test_pattern_case_insensitive() {
97        test_pattern!("*.{js,PY}", "src/hello.py", true, i:true);
98        test_pattern!("*.{js,PY}", "src/hello.PY", true, i:true);
99        test_pattern!("*.{js,PY}", "src/hello.JS", true, i:true);
100        test_pattern!("*.{js,PY}", "src/hello.js", true, i:true);
101        test_pattern!("", "", false, i:true);
102        test_pattern!("*", "", true, i:true);
103    }
104
105    #[test]
106    fn test_pattern_to_string() {
107        let pattern = unsafe { relay_pattern_new(&RelayStr::new("Foo**"), true, u64::MAX) };
108        let mut formatted = unsafe { relay_pattern_to_string(pattern) };
109        unsafe { relay_pattern_free(pattern) };
110
111        assert_eq!(unsafe { formatted.as_str() }, "foo*");
112        unsafe { formatted.free() };
113    }
114
115    #[test]
116    fn test_invalid_patterns() {
117        relay_err_clear();
118
119        let result = unsafe { relay_pattern_new(&RelayStr::new("["), false, u64::MAX) };
120        assert!(result.is_null());
121        assert!(matches!(
122            relay_err_get_last_code(),
123            RelayErrorCode::PatternError
124        ));
125
126        let mut message = relay_err_get_last_message();
127        assert!(unsafe { message.as_str() }.contains("Unbalanced character class"));
128        unsafe { message.free() };
129        relay_err_clear();
130    }
131}