Skip to main content

cloudiful_redactor/redactor/
mod.rs

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