cloudiful_redactor/redactor/
mod.rs1use 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}