shadowforge_lib/adapters/
scrubber.rs1use crate::domain::errors::ScrubberError;
4use crate::domain::ports::StyloScrubber;
5use crate::domain::scrubber;
6use crate::domain::types::StyloProfile;
7
8pub struct StyloScrubberImpl;
12
13impl StyloScrubberImpl {
14 #[must_use]
16 pub const fn new() -> Self {
17 Self
18 }
19}
20
21impl Default for StyloScrubberImpl {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl StyloScrubber for StyloScrubberImpl {
28 fn scrub(&self, text: &str, profile: &StyloProfile) -> Result<String, ScrubberError> {
29 let result = scrubber::scrub_text(text, profile);
30
31 if result.is_empty() && !text.is_empty() {
34 return Err(ScrubberError::ProfileNotSatisfied {
36 reason: "scrubbing reduced non-empty input to empty output".into(),
37 });
38 }
39
40 Ok(result)
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 type TestResult = Result<(), Box<dyn std::error::Error>>;
49
50 fn profile() -> StyloProfile {
51 StyloProfile {
52 target_vocab_size: 5000,
53 target_avg_sentence_len: 15.0,
54 normalize_punctuation: true,
55 }
56 }
57
58 #[test]
59 fn scrub_via_adapter() -> TestResult {
60 let scrubber = StyloScrubberImpl::new();
61 let input = "Don\u{2019}t worry\u{2014}it\u{2019}s fine!";
62 let result = scrubber.scrub(input, &profile())?;
63 assert!(result.contains("do not"));
64 assert!(result.contains("it is"));
65 assert!(!result.contains('\u{2014}'));
66 Ok(())
67 }
68
69 #[test]
70 fn scrub_empty_returns_empty() -> TestResult {
71 let scrubber = StyloScrubberImpl::new();
72 let result = scrubber.scrub("", &profile())?;
73 assert!(result.is_empty());
74 Ok(())
75 }
76
77 #[test]
78 fn scrub_idempotent_via_adapter() -> TestResult {
79 let scrubber = StyloScrubberImpl::new();
80 let input = "\u{201C}They\u{2019}re coming,\u{201D} she whispered\u{2026}";
81 let once = scrubber.scrub(input, &profile())?;
82 let twice = scrubber.scrub(&once, &profile())?;
83 assert_eq!(once, twice);
84 Ok(())
85 }
86
87 #[test]
88 fn scrub_preserves_non_latin() -> TestResult {
89 let scrubber = StyloScrubberImpl::new();
90 let input = "\u{0645}\u{0631}\u{062D}\u{0628}\u{0627} \u{0628}\u{0627}\u{0644}\u{0639}\u{0627}\u{0644}\u{0645}";
91 let result = scrubber.scrub(input, &profile())?;
92 assert!(!result.is_empty());
93 Ok(())
94 }
95
96 #[test]
97 fn default_impl() -> TestResult {
98 let scrubber = StyloScrubberImpl;
99 let result = scrubber.scrub("hello world", &profile())?;
100 assert_eq!(result, "hello world");
101 Ok(())
102 }
103}