use crate::scanner::RuleResult;
use crate::{
CompiledRule, CreateScannerError, Labels, MatchAction, Path, RootRuleConfig, RuleConfig,
ScannerBuilder, ScannerError, StringMatch, StringMatchesCtx,
};
use futures::executor::block_on;
use std::sync::Arc;
use std::time::Duration;
use tokio::task::block_in_place;
pub struct AsyncRuleConfig {
wait: Duration,
}
pub struct AsyncCompiledRule {
wait: Duration,
}
impl CompiledRule for AsyncCompiledRule {
fn get_string_matches(
&self,
_content: &str,
_path: &Path,
ctx: &mut StringMatchesCtx,
) -> RuleResult {
let wait = self.wait;
ctx.process_async(move |ctx| {
Box::pin(async move {
tokio::time::sleep(wait).await;
ctx.emit_match(StringMatch {
start: 10,
end: 16,
keyword: None,
});
Ok(())
})
})
}
}
impl RuleConfig for AsyncRuleConfig {
fn convert_to_compiled_rule(
&self,
_content: usize,
_: Labels,
) -> Result<Box<dyn CompiledRule>, CreateScannerError> {
Ok(Box::new(AsyncCompiledRule { wait: self.wait }))
}
}
#[tokio::test(flavor = "multi_thread")]
async fn run_async_rule() {
let scanner = ScannerBuilder::new(&[RootRuleConfig::new(Arc::new(AsyncRuleConfig {
wait: Duration::from_millis(1),
}) as Arc<dyn RuleConfig>)
.match_action(MatchAction::Redact {
replacement: "[REDACTED]".to_string(),
})])
.build()
.unwrap();
block_in_place(|| {
let mut input = "this is a secret with random data".to_owned();
let matched_rules = scanner.scan(&mut input).unwrap();
assert_eq!(matched_rules.len(), 1);
assert_eq!(input, "this is a [REDACTED] with random data");
});
let mut input = "this is a secret with random data".to_owned();
let matched_rules = scanner.scan_async(&mut input).await.unwrap();
assert_eq!(matched_rules.len(), 1);
assert_eq!(input, "this is a [REDACTED] with random data");
}
#[tokio::test(flavor = "multi_thread")]
async fn async_scan_timeout() {
let scanner = ScannerBuilder::new(&[RootRuleConfig::new(Arc::new(AsyncRuleConfig {
wait: Duration::from_secs(99999),
}) as Arc<dyn RuleConfig>)
.match_action(MatchAction::Redact {
replacement: "[REDACTED]".to_string(),
})])
.with_async_scan_timeout(Duration::from_millis(1))
.build()
.unwrap();
let mut input = "this is a secret with random data".to_owned();
let result = scanner.scan_async(&mut input).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
ScannerError::Transient("Async scan timeout".to_string())
);
}
#[test]
fn async_scan_outside_of_tokio() {
let scanner = ScannerBuilder::new(&[RootRuleConfig::new(Arc::new(AsyncRuleConfig {
wait: Duration::from_millis(1),
}) as Arc<dyn RuleConfig>)
.match_action(MatchAction::Redact {
replacement: "[REDACTED]".to_string(),
})])
.build()
.unwrap();
let fut = async move {
let mut input = "this is a secret with random data".to_owned();
let matched_rules = scanner.scan_async(&mut input).await.unwrap();
assert_eq!(matched_rules.len(), 1);
assert_eq!(input, "this is a [REDACTED] with random data");
};
std::thread::spawn(move || {
block_on(fut);
})
.join()
.unwrap();
}