1use serde::{Deserialize, Serialize};
2use std::ops::Range;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum FindingKind {
7 Secret,
8 Domain,
9 Url,
10 Email,
11 Ip,
12 Cidr,
13 Phone,
14 Person,
15 Organization,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19struct FindingKindMeta {
20 label: &'static str,
21 token_label: &'static str,
22 priority: u8,
23 containment_priority: u8,
24}
25
26impl FindingKind {
27 const fn meta(self) -> FindingKindMeta {
28 match self {
29 Self::Secret => FindingKindMeta {
30 label: "secret",
31 token_label: "SECRET",
32 priority: 100,
33 containment_priority: 75,
34 },
35 Self::Domain => FindingKindMeta {
36 label: "domain",
37 token_label: "DOMAIN",
38 priority: 70,
39 containment_priority: 80,
40 },
41 Self::Url => FindingKindMeta {
42 label: "url",
43 token_label: "URL",
44 priority: 90,
45 containment_priority: 100,
46 },
47 Self::Email => FindingKindMeta {
48 label: "email",
49 token_label: "EMAIL",
50 priority: 85,
51 containment_priority: 95,
52 },
53 Self::Ip => FindingKindMeta {
54 label: "ip",
55 token_label: "IP",
56 priority: 75,
57 containment_priority: 85,
58 },
59 Self::Cidr => FindingKindMeta {
60 label: "cidr",
61 token_label: "CIDR",
62 priority: 80,
63 containment_priority: 90,
64 },
65 Self::Phone => FindingKindMeta {
66 label: "phone",
67 token_label: "PHONE",
68 priority: 60,
69 containment_priority: 70,
70 },
71 Self::Person => FindingKindMeta {
72 label: "person",
73 token_label: "PERSON",
74 priority: 50,
75 containment_priority: 50,
76 },
77 Self::Organization => FindingKindMeta {
78 label: "organization",
79 token_label: "ORG",
80 priority: 45,
81 containment_priority: 45,
82 },
83 }
84 }
85
86 pub fn label(self) -> &'static str {
87 self.meta().label
88 }
89
90 pub fn token_label(self) -> &'static str {
91 self.meta().token_label
92 }
93
94 pub fn priority(self) -> u8 {
95 self.meta().priority
96 }
97
98 pub fn containment_priority(self) -> u8 {
99 self.meta().containment_priority
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "snake_case")]
105pub enum FindingSource {
106 Rule,
107 Llm,
108}
109
110impl FindingSource {
111 pub fn bonus(self) -> u8 {
112 match self {
113 Self::Rule => 10,
114 Self::Llm => 0,
115 }
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct Finding {
121 pub kind: FindingKind,
122 pub source: FindingSource,
123 pub match_text: String,
124 pub normalized_key: String,
125 pub confidence: u8,
126 pub start: usize,
127 pub end: usize,
128}
129
130impl Finding {
131 pub fn range(&self) -> Range<usize> {
132 self.start..self.end
133 }
134
135 pub fn score(&self) -> u16 {
136 u16::from(self.kind.priority())
137 + u16::from(self.source.bonus())
138 + u16::from(self.confidence)
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum ReplacementStrategy {
145 StructuredToken,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct AppliedReplacement {
150 pub kind: FindingKind,
151 #[serde(skip_serializing)]
152 pub original: String,
153 pub replacement: String,
154 pub strategy: ReplacementStrategy,
155 pub display_value: Option<String>,
156}
157
158#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
159pub struct RedactionStats {
160 pub total_findings: usize,
161 pub applied_replacements: usize,
162 pub dropped_findings: usize,
163 pub llm_configured: bool,
164 pub llm_request_failed: bool,
165 pub llm_candidates_accepted: usize,
166 pub llm_candidates_rejected: usize,
167 pub llm_error: Option<String>,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171pub struct RedactionResult {
172 pub redacted_text: String,
173 pub findings: Vec<Finding>,
174 pub applied_replacements: Vec<AppliedReplacement>,
175 pub stats: RedactionStats,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct RedactionArtifact {
180 pub result: RedactionResult,
181 pub session: RedactionSession,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub struct RestorationEntry {
186 pub token: String,
187 pub kind: FindingKind,
188 pub original: String,
189 pub replacement_hint: Option<String>,
190 pub occurrences: usize,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct RedactionSession {
195 pub version: u32,
196 pub session_id: String,
197 pub fingerprint: String,
198 pub redacted_fingerprint: String,
199 pub redacted_text: String,
200 pub entries: Vec<RestorationEntry>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204pub struct RestoreResult {
205 pub restored_text: String,
206 pub restored_count: usize,
207 pub unresolved_tokens: Vec<String>,
208 pub validation_errors: Vec<String>,
209}
210
211impl RestoreResult {
212 pub fn is_valid(&self) -> bool {
213 self.validation_errors.is_empty() && self.unresolved_tokens.is_empty()
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218pub struct SessionEntrySummary {
219 pub token: String,
220 pub kind: FindingKind,
221 pub replacement_hint: Option<String>,
222 pub occurrences: usize,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226pub struct SessionSummary {
227 pub version: u32,
228 pub session_id: String,
229 pub fingerprint: String,
230 pub redacted_fingerprint: String,
231 pub entry_count: usize,
232 pub entries: Vec<SessionEntrySummary>,
233}