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 =
85            self.redact_artifact_with_input_kind_and_source(text, InputKind::Text, Some(source_path))?;
86        Ok(artifact.result)
87    }
88
89    pub fn redact_artifact(&self, text: &str) -> Result<RedactionArtifact, RedactorError> {
90        self.redact_artifact_with_input_kind(text, InputKind::Text)
91    }
92
93    pub fn redact_artifact_with_input_kind(
94        &self,
95        text: &str,
96        input_kind: InputKind,
97    ) -> Result<RedactionArtifact, RedactorError> {
98        self.redact_artifact_with_input_kind_and_source(text, input_kind, None)
99    }
100
101    pub fn redact_artifact_with_input_kind_and_source(
102        &self,
103        text: &str,
104        input_kind: InputKind,
105        source_path: Option<&str>,
106    ) -> Result<RedactionArtifact, RedactorError> {
107        let outcome = detect_internal(self, text, input_kind, source_path);
108        let findings = outcome.findings;
109        let output = crate::replace::apply_replacements(text, &findings, &self.policy);
110        let stats = stats_for(self.llm.is_some(), &findings, outcome.stats);
111
112        Ok(RedactionArtifact {
113            result: RedactionResult {
114                redacted_text: output.redacted_text,
115                findings,
116                applied_replacements: output.applied_replacements,
117                stats,
118            },
119            session: output.session,
120        })
121    }
122
123    pub fn redact_with_session(&self, text: &str) -> Result<RedactionSession, RedactorError> {
124        self.redact_with_session_input_kind(text, InputKind::Text)
125    }
126
127    pub fn redact_with_session_input_kind(
128        &self,
129        text: &str,
130        input_kind: InputKind,
131    ) -> Result<RedactionSession, RedactorError> {
132        Ok(self
133            .redact_artifact_with_input_kind(text, input_kind)?
134            .session)
135    }
136
137    pub fn detect(&self, text: &str) -> Result<Vec<Finding>, RedactorError> {
138        self.detect_with_input_kind(text, InputKind::Text)
139    }
140
141    pub fn detect_with_input_kind(
142        &self,
143        text: &str,
144        input_kind: InputKind,
145    ) -> Result<Vec<Finding>, RedactorError> {
146        Ok(detect_internal(self, text, input_kind, None).findings)
147    }
148
149    pub fn detect_with_source_path(
150        &self,
151        text: &str,
152        source_path: &str,
153    ) -> Result<Vec<Finding>, RedactorError> {
154        Ok(detect_internal(self, text, InputKind::Text, Some(source_path))
155            .findings)
156    }
157
158    pub fn restore_text(&self, text: &str, session: &RedactionSession) -> RestoreResult {
159        restore_text_with_session(text, session)
160    }
161
162    pub fn restore_patch(&self, patch: &str, session: &RedactionSession) -> RestoreResult {
163        restore_patch_with_session(patch, session)
164    }
165}
166
167impl SessionRedactor {
168    pub fn new() -> Self {
169        Self::default()
170    }
171
172    pub fn redact_fragment(
173        &mut self,
174        redactor: &Redactor,
175        text: &str,
176    ) -> Result<String, RedactorError> {
177        self.redact_text_fragment(redactor, text)
178    }
179
180    pub fn build_session(&self, original_text: &str, redacted_text: &str) -> RedactionSession {
181        self.build_redaction_session(
182            original_text,
183            redacted_text,
184            &crate::RedactionPolicy::default(),
185        )
186    }
187
188    pub fn max_token_len(&self) -> usize {
189        self.max_replacement_token_len()
190    }
191}