use aho_corasick::AhoCorasick;
use crate::patterns::{Pattern, build_ac_automaton};
use crate::swap::swap_with_ac;
use crate::types::{SwapError, SwapResult};
pub struct Detector {
patterns: Vec<Pattern>,
ac: AhoCorasick,
}
impl Detector {
#[must_use = "the Detector should be stored and reused across requests; \
discarding it wastes the cost of building the AC automaton"]
pub fn new(patterns: Vec<Pattern>) -> Self {
let ac = build_ac_automaton(&patterns);
Self { patterns, ac }
}
pub fn swap(&self, payload: &[u8]) -> Result<SwapResult, SwapError> {
swap_with_ac(payload, &self.patterns, &self.ac)
}
}
impl std::fmt::Debug for Detector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Detector")
.field("pattern_count", &self.patterns.len())
.finish_non_exhaustive()
}
}
fn _assert_detector_send_sync()
where
Detector: Send + Sync,
{
}
#[cfg(test)]
mod tests {
use super::*;
use crate::patterns;
const TEST_ANT: &[u8] = b"sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
#[test]
fn test_detector_swap_semantics_identical_to_free_swap() {
let pat = patterns::anthropic();
let payload = [b"token: ".as_slice(), TEST_ANT].concat();
let detector = Detector::new(vec![pat.clone()]);
let r_detector = detector.swap(&payload).unwrap();
let r_free = crate::swap::swap(&payload, &[pat]).unwrap();
assert_eq!(
r_detector.entries[0].fake, r_free.entries[0].fake,
"INV-39: fakes must match"
);
assert_eq!(
r_detector.payload, r_free.payload,
"INV-39: swapped payloads must match"
);
assert_eq!(r_detector.entries.len(), r_free.entries.len());
}
#[test]
fn test_detector_multiple_calls_produce_correct_results() {
let detector = Detector::new(patterns::all());
let payload_a = [b"token: ".as_slice(), TEST_ANT].concat();
let payload_b = b"no secrets here".as_ref();
let r_a = detector.swap(&payload_a).unwrap();
let r_b = detector.swap(payload_b).unwrap();
assert_eq!(r_a.entries.len(), 1, "secret in payload_a must be detected");
assert_eq!(r_b.entries.len(), 0, "no secret in payload_b");
}
#[test]
fn test_detector_is_send_sync() {
fn assert_send_sync<T: Send + Sync + 'static>() {}
assert_send_sync::<Detector>();
}
#[test]
fn test_detector_empty_patterns() {
let detector = Detector::new(vec![]);
let payload = b"hello world";
let r = detector.swap(payload).unwrap();
assert_eq!(r.payload, payload.as_ref());
assert!(r.entries.is_empty());
}
}