Skip to main content

relay_cabi/
pattern.rs

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