1pub mod manager;
8
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use thiserror::Error;
13use uuid::Uuid;
14
15#[derive(Debug, Error)]
17pub enum ConsentError {
18 #[error("Consent record not found: {0}")]
19 ConsentNotFound(String),
20
21 #[error("Invalid consent data: {0}")]
22 InvalidData(String),
23
24 #[error("Consent already exists: {0}")]
25 ConsentExists(String),
26
27 #[error("Storage error: {0}")]
28 StorageError(String),
29
30 #[error("Serialization error: {0}")]
31 SerializationError(#[from] serde_json::Error),
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
36pub enum ConsentType {
37 DataProcessing,
39
40 Marketing,
42
43 Analytics,
45
46 DataSharing,
48
49 AutomatedDecisionMaking,
51
52 SessionStorage,
54
55 AuditLogging,
57
58 Custom(String),
60}
61
62impl std::fmt::Display for ConsentType {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 ConsentType::DataProcessing => write!(f, "Data Processing"),
66 ConsentType::Marketing => write!(f, "Marketing Communications"),
67 ConsentType::Analytics => write!(f, "Analytics & Performance"),
68 ConsentType::DataSharing => write!(f, "Third-party Data Sharing"),
69 ConsentType::AutomatedDecisionMaking => write!(f, "Automated Decision Making"),
70 ConsentType::SessionStorage => write!(f, "Session Storage"),
71 ConsentType::AuditLogging => write!(f, "Audit Logging"),
72 ConsentType::Custom(desc) => write!(f, "Custom: {desc}"),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79pub enum LegalBasis {
80 Consent,
82
83 Contract,
85
86 LegalObligation,
88
89 VitalInterests,
91
92 PublicTask,
94
95 LegitimateInterests,
97}
98
99impl std::fmt::Display for LegalBasis {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 match self {
102 LegalBasis::Consent => write!(f, "Consent (GDPR 6.1.a)"),
103 LegalBasis::Contract => write!(f, "Contract (GDPR 6.1.b)"),
104 LegalBasis::LegalObligation => write!(f, "Legal Obligation (GDPR 6.1.c)"),
105 LegalBasis::VitalInterests => write!(f, "Vital Interests (GDPR 6.1.d)"),
106 LegalBasis::PublicTask => write!(f, "Public Task (GDPR 6.1.e)"),
107 LegalBasis::LegitimateInterests => write!(f, "Legitimate Interests (GDPR 6.1.f)"),
108 }
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114pub enum ConsentStatus {
115 Granted,
117
118 Withdrawn,
120
121 Pending,
123
124 Expired,
126
127 Denied,
129}
130
131impl std::fmt::Display for ConsentStatus {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 match self {
134 ConsentStatus::Granted => write!(f, "Granted"),
135 ConsentStatus::Withdrawn => write!(f, "Withdrawn"),
136 ConsentStatus::Pending => write!(f, "Pending"),
137 ConsentStatus::Expired => write!(f, "Expired"),
138 ConsentStatus::Denied => write!(f, "Denied"),
139 }
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ConsentRecord {
146 pub id: String,
148
149 pub subject_id: String,
151
152 pub consent_type: ConsentType,
154
155 pub status: ConsentStatus,
157
158 pub legal_basis: LegalBasis,
160
161 pub purpose: String,
163
164 pub data_categories: Vec<String>,
166
167 pub granted_at: Option<DateTime<Utc>>,
169
170 pub withdrawn_at: Option<DateTime<Utc>>,
172
173 pub expires_at: Option<DateTime<Utc>>,
175
176 pub consent_source: String,
178
179 pub source_ip: Option<String>,
181
182 pub metadata: HashMap<String, String>,
184
185 pub created_at: DateTime<Utc>,
187
188 pub updated_at: DateTime<Utc>,
190}
191
192impl ConsentRecord {
193 pub fn new(
195 subject_id: String,
196 consent_type: ConsentType,
197 legal_basis: LegalBasis,
198 purpose: String,
199 consent_source: String,
200 ) -> Self {
201 let now = Utc::now();
202 Self {
203 id: Uuid::new_v4().to_string(),
204 subject_id,
205 consent_type,
206 status: ConsentStatus::Pending,
207 legal_basis,
208 purpose,
209 data_categories: Vec::new(),
210 granted_at: None,
211 withdrawn_at: None,
212 expires_at: None,
213 consent_source,
214 source_ip: None,
215 metadata: HashMap::new(),
216 created_at: now,
217 updated_at: now,
218 }
219 }
220
221 pub fn grant(&mut self, source_ip: Option<String>) {
223 self.status = ConsentStatus::Granted;
224 self.granted_at = Some(Utc::now());
225 self.withdrawn_at = None;
226 self.source_ip = source_ip;
227 self.updated_at = Utc::now();
228 }
229
230 pub fn withdraw(&mut self, source_ip: Option<String>) {
232 self.status = ConsentStatus::Withdrawn;
233 self.withdrawn_at = Some(Utc::now());
234 self.source_ip = source_ip;
235 self.updated_at = Utc::now();
236 }
237
238 pub fn deny(&mut self, source_ip: Option<String>) {
240 self.status = ConsentStatus::Denied;
241 self.source_ip = source_ip;
242 self.updated_at = Utc::now();
243 }
244
245 pub fn is_valid(&self) -> bool {
247 match self.status {
248 ConsentStatus::Granted => {
249 if let Some(expires_at) = self.expires_at {
251 Utc::now() < expires_at
252 } else {
253 true
254 }
255 }
256 _ => false,
257 }
258 }
259
260 pub fn is_expired(&self) -> bool {
262 if let Some(expires_at) = self.expires_at {
263 Utc::now() >= expires_at
264 } else {
265 false
266 }
267 }
268
269 pub fn set_expiration(&mut self, expires_at: DateTime<Utc>) {
271 self.expires_at = Some(expires_at);
272 self.updated_at = Utc::now();
273 }
274
275 pub fn add_data_category(&mut self, category: String) {
277 if !self.data_categories.contains(&category) {
278 self.data_categories.push(category);
279 self.updated_at = Utc::now();
280 }
281 }
282
283 pub fn add_metadata(&mut self, key: String, value: String) {
285 self.metadata.insert(key, value);
286 self.updated_at = Utc::now();
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ConsentAuditEntry {
293 pub id: String,
295
296 pub consent_id: String,
298
299 pub subject_id: String,
301
302 pub action: String,
304
305 pub previous_status: Option<ConsentStatus>,
307
308 pub new_status: ConsentStatus,
310
311 pub action_source: String,
313
314 pub source_ip: Option<String>,
316
317 pub details: HashMap<String, String>,
319
320 pub timestamp: DateTime<Utc>,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct ConsentSummary {
327 pub subject_id: String,
329
330 pub consents: HashMap<ConsentType, ConsentStatus>,
332
333 pub is_valid: bool,
335
336 pub last_updated: DateTime<Utc>,
338
339 pub pending_requests: usize,
341
342 pub expired_consents: usize,
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_consent_record_creation() {
352 let record = ConsentRecord::new(
353 "user123".to_string(),
354 ConsentType::DataProcessing,
355 LegalBasis::Consent,
356 "Process user authentication data".to_string(),
357 "web_form".to_string(),
358 );
359
360 assert_eq!(record.subject_id, "user123");
361 assert_eq!(record.consent_type, ConsentType::DataProcessing);
362 assert_eq!(record.status, ConsentStatus::Pending);
363 assert_eq!(record.legal_basis, LegalBasis::Consent);
364 assert!(record.granted_at.is_none());
365 assert!(!record.is_valid());
366 }
367
368 #[test]
369 fn test_consent_grant_and_withdraw() {
370 let mut record = ConsentRecord::new(
371 "user123".to_string(),
372 ConsentType::Analytics,
373 LegalBasis::Consent,
374 "Analytics tracking".to_string(),
375 "api".to_string(),
376 );
377
378 record.grant(Some("192.168.1.100".to_string()));
380 assert_eq!(record.status, ConsentStatus::Granted);
381 assert!(record.granted_at.is_some());
382 assert!(record.is_valid());
383
384 record.withdraw(Some("192.168.1.100".to_string()));
386 assert_eq!(record.status, ConsentStatus::Withdrawn);
387 assert!(record.withdrawn_at.is_some());
388 assert!(!record.is_valid());
389 }
390
391 #[test]
392 fn test_consent_expiration() {
393 let mut record = ConsentRecord::new(
394 "user123".to_string(),
395 ConsentType::Marketing,
396 LegalBasis::Consent,
397 "Marketing emails".to_string(),
398 "web_form".to_string(),
399 );
400
401 record.grant(None);
403 assert!(record.is_valid());
404
405 record.set_expiration(Utc::now() - chrono::Duration::hours(1));
407 assert!(!record.is_valid());
408 assert!(record.is_expired());
409 }
410
411 #[test]
412 fn test_consent_type_display() {
413 assert_eq!(ConsentType::DataProcessing.to_string(), "Data Processing");
414 assert_eq!(
415 ConsentType::Custom("Special Processing".to_string()).to_string(),
416 "Custom: Special Processing"
417 );
418 }
419
420 #[test]
421 fn test_legal_basis_display() {
422 assert_eq!(LegalBasis::Consent.to_string(), "Consent (GDPR 6.1.a)");
423 assert_eq!(
424 LegalBasis::LegitimateInterests.to_string(),
425 "Legitimate Interests (GDPR 6.1.f)"
426 );
427 }
428
429 #[test]
430 fn test_data_categories() {
431 let mut record = ConsentRecord::new(
432 "user123".to_string(),
433 ConsentType::DataProcessing,
434 LegalBasis::Consent,
435 "User data processing".to_string(),
436 "api".to_string(),
437 );
438
439 record.add_data_category("personal_identifiers".to_string());
440 record.add_data_category("authentication_data".to_string());
441 record.add_data_category("personal_identifiers".to_string()); assert_eq!(record.data_categories.len(), 2);
444 assert!(
445 record
446 .data_categories
447 .contains(&"personal_identifiers".to_string())
448 );
449 assert!(
450 record
451 .data_categories
452 .contains(&"authentication_data".to_string())
453 );
454 }
455}