1use crate::types::{EntityType, RecognizerResult};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Policy {
12 pub id: String,
13 pub name: String,
14 pub display_name: String,
15 pub organization_id: String,
16 pub status: PolicyStatus,
17 pub priority: u32,
18 pub description: String,
19 pub conditions: Vec<PolicyCondition>,
20 pub pattern_rules: Vec<PatternRule>,
21 pub redaction_config: RedactionConfig,
22 pub actions: PolicyActions,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum PolicyStatus {
28 Active,
29 Inactive,
30 Draft,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct PolicyCondition {
35 pub field: String,
36 pub operator: ConditionOperator,
37 pub value: serde_json::Value,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum ConditionOperator {
43 Equals,
44 NotEquals,
45 Contains,
46 NotContains,
47 GreaterThan,
48 LessThan,
49 Regex,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PatternRule {
54 pub pattern_id: String,
55 pub name: String,
56 pub enabled: bool,
57 pub mode: String,
58 pub strategy: String,
59 pub confidence: f32,
60 pub replacement: String,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct RedactionConfig {
65 pub default_mode: String,
66 pub enabled_categories: Vec<String>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct PolicyActions {
71 pub action: String,
72 pub redact_fields: Vec<String>,
73}
74
75impl Policy {
76 pub fn apply(&self, results: Vec<RecognizerResult>) -> Vec<RecognizerResult> {
78 if self.status != PolicyStatus::Active {
79 return results;
80 }
81
82 let mut rule_map: HashMap<String, &PatternRule> = HashMap::new();
84 for rule in &self.pattern_rules {
85 if rule.enabled {
86 rule_map.insert(rule.pattern_id.clone(), rule);
87 }
88 }
89
90 results
92 .into_iter()
93 .filter_map(|mut result| {
94 let entity_id = result.entity_type.as_str();
95
96 if let Some(rule) = rule_map.get(entity_id) {
98 if result.score >= rule.confidence {
100 result.score = result.score.max(rule.confidence);
102 Some(result)
103 } else {
104 None
105 }
106 } else {
107 Some(result)
109 }
110 })
111 .collect()
112 }
113
114 pub fn enabled_entity_types(&self) -> Vec<EntityType> {
116 self.pattern_rules
117 .iter()
118 .filter(|rule| rule.enabled)
119 .map(|rule| {
120 EntityType::from(rule.pattern_id.clone())
122 })
123 .collect()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_policy_apply() {
133 let policy = Policy {
134 id: "test".to_string(),
135 name: "test".to_string(),
136 display_name: "Test Policy".to_string(),
137 organization_id: "org1".to_string(),
138 status: PolicyStatus::Active,
139 priority: 100,
140 description: "Test policy".to_string(),
141 conditions: vec![],
142 pattern_rules: vec![PatternRule {
143 pattern_id: "EMAIL_ADDRESS".to_string(),
144 name: "email".to_string(),
145 enabled: true,
146 mode: "replace".to_string(),
147 strategy: "semantic".to_string(),
148 confidence: 0.8,
149 replacement: "[EMAIL]".to_string(),
150 }],
151 redaction_config: RedactionConfig {
152 default_mode: "replace".to_string(),
153 enabled_categories: vec!["contact_info".to_string()],
154 },
155 actions: PolicyActions {
156 action: "redact".to_string(),
157 redact_fields: vec!["content".to_string()],
158 },
159 };
160
161 let results = vec![
162 RecognizerResult::new(EntityType::EmailAddress, 0, 10, 0.9, "test"),
163 RecognizerResult::new(EntityType::EmailAddress, 10, 20, 0.7, "test"),
164 ];
165
166 let filtered = policy.apply(results);
167
168 assert_eq!(filtered.len(), 1);
170 assert!(filtered[0].score >= 0.8);
171 }
172
173 #[test]
174 fn test_inactive_policy() {
175 let policy = Policy {
176 id: "test".to_string(),
177 name: "test".to_string(),
178 display_name: "Test Policy".to_string(),
179 organization_id: "org1".to_string(),
180 status: PolicyStatus::Inactive,
181 priority: 100,
182 description: "Test policy".to_string(),
183 conditions: vec![],
184 pattern_rules: vec![],
185 redaction_config: RedactionConfig {
186 default_mode: "replace".to_string(),
187 enabled_categories: vec![],
188 },
189 actions: PolicyActions {
190 action: "redact".to_string(),
191 redact_fields: vec![],
192 },
193 };
194
195 let results = vec![RecognizerResult::new(
196 EntityType::EmailAddress,
197 0,
198 10,
199 0.9,
200 "test",
201 )];
202
203 let filtered = policy.apply(results.clone());
204
205 assert_eq!(filtered.len(), results.len());
207 }
208}