Skip to main content

cloudiful_redactor/redactor/
mod.rs

1use std::sync::Arc;
2
3use crate::{
4    Finding, InputKind, LlmConfig, RedactionArtifact, RedactionPolicy, RedactionResult,
5    RedactionRules, RedactionSession, RedactorError, RestoreResult, restore_patch_with_session,
6    restore_text_with_session,
7};
8
9mod detection;
10mod session;
11mod stats;
12
13use detection::detect_internal;
14use session::SessionRedactorExt;
15use stats::stats_for;
16
17#[derive(Debug, Clone, Default)]
18pub struct RedactorBuilder {
19    llm: Option<LlmConfig>,
20    policy: RedactionPolicy,
21}
22
23impl RedactorBuilder {
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    pub fn with_llm(mut self, config: LlmConfig) -> Self {
29        self.llm = Some(config);
30        self
31    }
32
33    pub fn with_person_detection(mut self, enabled: bool) -> Self {
34        self.policy.rules.person = enabled;
35        self
36    }
37
38    pub fn with_redaction_rules(mut self, rules: RedactionRules) -> Self {
39        self.policy.rules = rules;
40        self
41    }
42
43    pub fn with_redaction_policy(mut self, policy: RedactionPolicy) -> Self {
44        self.policy = policy;
45        self
46    }
47
48    pub fn build(self) -> Redactor {
49        let custom_strings = crate::detect::CompiledCustomStrings::new(&self.policy.custom_strings);
50        Redactor {
51            llm: self.llm,
52            compiled_policy: Arc::new(CompiledPolicy {
53                policy: self.policy,
54                custom_strings,
55            }),
56        }
57    }
58}
59
60#[derive(Debug, Clone)]
61pub struct Redactor {
62    pub(super) llm: Option<LlmConfig>,
63    compiled_policy: Arc<CompiledPolicy>,
64}
65
66#[derive(Debug)]
67struct CompiledPolicy {
68    policy: RedactionPolicy,
69    custom_strings: crate::detect::CompiledCustomStrings,
70}
71
72#[derive(Debug, Default)]
73pub struct SessionRedactor {
74    pub(super) processor: crate::replace::ReplacementProcessor,
75}
76
77impl Redactor {
78    pub fn policy(&self) -> &RedactionPolicy {
79        &self.compiled_policy.policy
80    }
81
82    pub fn redact(&self, text: &str) -> Result<RedactionResult, RedactorError> {
83        self.redact_with_input_kind(text, InputKind::Text)
84    }
85
86    pub fn redact_with_input_kind(
87        &self,
88        text: &str,
89        input_kind: InputKind,
90    ) -> Result<RedactionResult, RedactorError> {
91        let artifact = self.redact_artifact_with_input_kind(text, input_kind)?;
92        Ok(artifact.result)
93    }
94
95    pub fn redact_with_source_path(
96        &self,
97        text: &str,
98        source_path: &str,
99    ) -> Result<RedactionResult, RedactorError> {
100        let artifact = self.redact_artifact_with_input_kind_and_source(
101            text,
102            InputKind::Text,
103            Some(source_path),
104        )?;
105        Ok(artifact.result)
106    }
107
108    pub fn redact_artifact(&self, text: &str) -> Result<RedactionArtifact, RedactorError> {
109        self.redact_artifact_with_input_kind(text, InputKind::Text)
110    }
111
112    pub fn redact_artifact_with_input_kind(
113        &self,
114        text: &str,
115        input_kind: InputKind,
116    ) -> Result<RedactionArtifact, RedactorError> {
117        self.redact_artifact_with_input_kind_and_source(text, input_kind, None)
118    }
119
120    pub fn redact_artifact_with_input_kind_and_source(
121        &self,
122        text: &str,
123        input_kind: InputKind,
124        source_path: Option<&str>,
125    ) -> Result<RedactionArtifact, RedactorError> {
126        self.redact_artifact_with_input_kind_source_and_prior_session(
127            text,
128            input_kind,
129            source_path,
130            None,
131            None,
132        )
133    }
134
135    pub fn redact_artifact_with_input_kind_source_and_prior_session(
136        &self,
137        text: &str,
138        input_kind: InputKind,
139        source_path: Option<&str>,
140        prior_session: Option<&RedactionSession>,
141        external_id: Option<&str>,
142    ) -> Result<RedactionArtifact, RedactorError> {
143        let outcome = detect_internal(self, text, input_kind, source_path);
144        let findings = outcome.findings;
145        let mut processor =
146            crate::replace::ReplacementProcessor::with_prior_session(prior_session, external_id)?;
147        let redacted_text = processor.redact_fragment(text, &findings);
148        let session = processor.build_session(text, &redacted_text, self.policy());
149        let applied_replacements = processor.into_applied_replacements();
150        let stats = stats_for(self.llm.is_some(), &findings, outcome.stats);
151
152        Ok(RedactionArtifact {
153            result: RedactionResult {
154                redacted_text: redacted_text.clone(),
155                findings,
156                applied_replacements,
157                stats,
158            },
159            session,
160        })
161    }
162
163    pub fn redact_with_session(&self, text: &str) -> Result<RedactionSession, RedactorError> {
164        self.redact_with_session_input_kind(text, InputKind::Text)
165    }
166
167    pub fn redact_with_session_input_kind(
168        &self,
169        text: &str,
170        input_kind: InputKind,
171    ) -> Result<RedactionSession, RedactorError> {
172        Ok(self
173            .redact_artifact_with_input_kind(text, input_kind)?
174            .session)
175    }
176
177    pub fn detect(&self, text: &str) -> Result<Vec<Finding>, RedactorError> {
178        self.detect_with_input_kind(text, InputKind::Text)
179    }
180
181    pub fn detect_with_input_kind(
182        &self,
183        text: &str,
184        input_kind: InputKind,
185    ) -> Result<Vec<Finding>, RedactorError> {
186        Ok(detect_internal(self, text, input_kind, None).findings)
187    }
188
189    pub fn detect_with_source_path(
190        &self,
191        text: &str,
192        source_path: &str,
193    ) -> Result<Vec<Finding>, RedactorError> {
194        Ok(detect_internal(self, text, InputKind::Text, Some(source_path)).findings)
195    }
196
197    pub fn restore_text(&self, text: &str, session: &RedactionSession) -> RestoreResult {
198        restore_text_with_session(text, session)
199    }
200
201    pub fn restore_patch(&self, patch: &str, session: &RedactionSession) -> RestoreResult {
202        restore_patch_with_session(patch, session)
203    }
204
205    pub fn redact_artifact_with_prior_session(
206        &self,
207        text: &str,
208        input_kind: InputKind,
209        prior_session: Option<&RedactionSession>,
210        external_id: Option<&str>,
211    ) -> Result<RedactionArtifact, RedactorError> {
212        self.redact_artifact_with_input_kind_source_and_prior_session(
213            text,
214            input_kind,
215            None,
216            prior_session,
217            external_id,
218        )
219    }
220}
221
222impl SessionRedactor {
223    pub fn new() -> Self {
224        Self::default()
225    }
226
227    pub fn with_prior_session(
228        prior_session: Option<&RedactionSession>,
229        external_id: Option<&str>,
230    ) -> Result<Self, RedactorError> {
231        Ok(Self {
232            processor: crate::replace::ReplacementProcessor::with_prior_session(
233                prior_session,
234                external_id,
235            )?,
236        })
237    }
238
239    pub fn redact_fragment(
240        &mut self,
241        redactor: &Redactor,
242        text: &str,
243    ) -> Result<String, RedactorError> {
244        self.redact_text_fragment(redactor, text)
245    }
246
247    pub fn redact_fragment_with_input_kind(
248        &mut self,
249        redactor: &Redactor,
250        text: &str,
251        input_kind: InputKind,
252    ) -> Result<String, RedactorError> {
253        self.redact_text_fragment_with_input_kind(redactor, text, input_kind)
254    }
255
256    pub fn finish_session(
257        &self,
258        original_text: &str,
259        redacted_text: &str,
260        policy: &RedactionPolicy,
261    ) -> RedactionSession {
262        self.build_redaction_session(original_text, redacted_text, policy)
263    }
264
265    pub fn has_applied_replacements(&self) -> bool {
266        self.processor.has_applied_replacements()
267    }
268
269    pub fn build_session(&self, original_text: &str, redacted_text: &str) -> RedactionSession {
270        self.build_redaction_session(
271            original_text,
272            redacted_text,
273            &crate::RedactionPolicy::default(),
274        )
275    }
276
277    pub fn max_token_len(&self) -> usize {
278        self.max_replacement_token_len()
279    }
280}