use crate::error::FormatParseError;
use fancy_regex::Regex;
use std::time::Instant;
const MAX_REGEX_COMPILATION_TIME_MS: u128 = 500;
pub fn build_regex(pattern: &str) -> Result<Regex, FormatParseError> {
let start = Instant::now();
let mut regex_with_flags = String::with_capacity(pattern.len() + 4);
regex_with_flags.push_str("(?s)");
regex_with_flags.push_str(pattern);
let regex = Regex::new(®ex_with_flags).map_err(|e| {
FormatParseError::RegexError(format!("Invalid regex pattern: {}", e))
})?;
let elapsed = start.elapsed().as_millis();
if elapsed > MAX_REGEX_COMPILATION_TIME_MS {
return Err(FormatParseError::RegexError(format!(
"Regex compilation took {}ms, exceeding maximum allowed time of {}ms",
elapsed, MAX_REGEX_COMPILATION_TIME_MS
)));
}
Ok(regex)
}
pub fn build_case_insensitive_regex(pattern: &str) -> Option<Regex> {
let start = Instant::now();
let mut regex_with_flags = String::with_capacity(pattern.len() + 8);
regex_with_flags.push_str("(?s)(?i)");
regex_with_flags.push_str(pattern);
let regex = Regex::new(®ex_with_flags).ok()?;
let elapsed = start.elapsed().as_millis();
if elapsed > MAX_REGEX_COMPILATION_TIME_MS {
return None;
}
Some(regex)
}
pub fn prepare_search_regex(regex_str: &str) -> String {
let mut start = 0;
let mut end = regex_str.len();
if regex_str.starts_with("(?s)") {
start = 4;
}
if regex_str[start..].starts_with("^") {
start += 1;
}
if regex_str[..end].ends_with("$") {
end -= 1;
}
if start > 0 || end < regex_str.len() {
regex_str[start..end].to_string()
} else {
regex_str.to_string()
}
}
pub fn build_search_regex(
regex_str: &str,
case_sensitive: bool,
) -> Result<Regex, FormatParseError> {
let start = Instant::now();
let search_regex_str = prepare_search_regex(regex_str);
let capacity = search_regex_str.len() + if case_sensitive { 4 } else { 8 };
let mut pattern = String::with_capacity(capacity);
pattern.push_str("(?s)");
if !case_sensitive {
pattern.push_str("(?i)");
}
pattern.push_str(&search_regex_str);
let regex = Regex::new(&pattern).map_err(|e| {
FormatParseError::RegexError(format!("Invalid regex pattern: {}", e))
})?;
let elapsed = start.elapsed().as_millis();
if elapsed > MAX_REGEX_COMPILATION_TIME_MS {
return Err(FormatParseError::RegexError(format!(
"Regex compilation took {}ms, exceeding maximum allowed time of {}ms",
elapsed, MAX_REGEX_COMPILATION_TIME_MS
)));
}
Ok(regex)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_regex() {
let regex = build_regex(r"^test$").unwrap();
assert!(regex.is_match("test").unwrap());
assert!(!regex.is_match("TEST").unwrap());
assert!(!regex.is_match("notest").unwrap());
}
#[test]
fn test_build_regex_with_dotall() {
let regex = build_regex(r"test.line").unwrap();
assert!(regex.is_match("test\nline").unwrap());
}
#[test]
fn test_build_case_insensitive_regex() {
let regex = build_case_insensitive_regex(r"^test$").unwrap();
assert!(regex.is_match("test").unwrap());
assert!(regex.is_match("TEST").unwrap());
assert!(regex.is_match("Test").unwrap());
assert!(!regex.is_match("notest").unwrap());
}
#[test]
fn test_build_case_insensitive_regex_with_dotall() {
let regex = build_case_insensitive_regex(r"test.line").unwrap();
assert!(regex.is_match("TEST\nLINE").unwrap());
}
#[test]
fn test_prepare_search_regex_no_anchors() {
let result = prepare_search_regex(r"test");
assert_eq!(result, "test");
}
#[test]
fn test_prepare_search_regex_with_anchors() {
let result = prepare_search_regex(r"^test$");
assert_eq!(result, "test");
}
#[test]
fn test_prepare_search_regex_with_dotall() {
let result = prepare_search_regex(r"(?s)^test$");
assert_eq!(result, "test");
}
#[test]
fn test_prepare_search_regex_start_anchor_only() {
let result = prepare_search_regex(r"^test");
assert_eq!(result, "test");
}
#[test]
fn test_prepare_search_regex_end_anchor_only() {
let result = prepare_search_regex(r"test$");
assert_eq!(result, "test");
}
#[test]
fn test_build_search_regex_case_sensitive() {
let regex = build_search_regex(r"^test$", true).unwrap();
assert!(regex.is_match("test").unwrap());
assert!(!regex.is_match("TEST").unwrap());
assert!(regex.is_match("prefix test suffix").unwrap());
}
#[test]
fn test_build_search_regex_case_insensitive() {
let regex = build_search_regex(r"^test$", false).unwrap();
assert!(regex.is_match("test").unwrap());
assert!(regex.is_match("TEST").unwrap());
assert!(regex.is_match("Test").unwrap());
assert!(regex.is_match("prefix TEST suffix").unwrap());
}
#[test]
fn test_build_search_regex_with_dotall() {
let regex = build_search_regex(r"test.line", true).unwrap();
assert!(regex.is_match("test\nline").unwrap());
assert!(regex.is_match("prefix test\nline suffix").unwrap());
}
}