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