1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ConsentStatus {
11 Active,
13 Expired,
15 Revoked,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ConsentRecord {
22 pub id: String,
24 pub subject: String,
26 pub recipient: String,
28 pub data_elements: Vec<String>,
30 pub purpose: String,
32 pub timestamp: String,
34 pub expires: Option<String>,
36 pub status: ConsentStatus,
38}
39
40impl ConsentRecord {
41 pub fn new(
43 id: impl Into<String>,
44 subject: impl Into<String>,
45 recipient: impl Into<String>,
46 data_elements: Vec<String>,
47 purpose: impl Into<String>,
48 timestamp: impl Into<String>,
49 expires: Option<String>,
50 ) -> Self {
51 Self {
52 id: id.into(),
53 subject: subject.into(),
54 recipient: recipient.into(),
55 data_elements,
56 purpose: purpose.into(),
57 timestamp: timestamp.into(),
58 expires,
59 status: ConsentStatus::Active,
60 }
61 }
62
63 pub fn is_valid(&self, now: &str) -> bool {
67 if self.status != ConsentStatus::Active {
68 return false;
69 }
70 if let Some(ref expires) = self.expires {
71 return now <= expires.as_str();
74 }
75 true
76 }
77
78 pub fn revoke(&mut self) {
80 self.status = ConsentStatus::Revoked;
81 }
82
83 pub fn check_expiry(&mut self, now: &str) {
85 if self.status == ConsentStatus::Active {
86 if let Some(ref expires) = self.expires {
87 if now > expires.as_str() {
88 self.status = ConsentStatus::Expired;
89 }
90 }
91 }
92 }
93
94 pub fn covers(&self, data_elements: &[&str], purpose: &str) -> bool {
96 if self.purpose != purpose {
97 return false;
98 }
99 data_elements
100 .iter()
101 .all(|elem| self.data_elements.iter().any(|d| d == elem))
102 }
103}
104
105pub struct ConsentManager {
107 records: Vec<ConsentRecord>,
108}
109
110impl ConsentManager {
111 pub fn new() -> Self {
113 Self {
114 records: Vec::new(),
115 }
116 }
117
118 pub fn record_consent(&mut self, consent: ConsentRecord) {
120 self.records.push(consent);
121 }
122
123 pub fn revoke_consent(&mut self, id: &str) -> bool {
125 if let Some(record) = self.records.iter_mut().find(|r| r.id == id) {
126 record.revoke();
127 true
128 } else {
129 false
130 }
131 }
132
133 pub fn get_consent(&self, id: &str) -> Option<&ConsentRecord> {
135 self.records.iter().find(|r| r.id == id)
136 }
137
138 pub fn find_valid_consents(
140 &self,
141 recipient: &str,
142 purpose: &str,
143 now: &str,
144 ) -> Vec<&ConsentRecord> {
145 self.records
146 .iter()
147 .filter(|r| r.recipient == recipient && r.purpose == purpose && r.is_valid(now))
148 .collect()
149 }
150
151 pub fn check_all_expiry(&mut self, now: &str) {
153 for record in &mut self.records {
154 record.check_expiry(now);
155 }
156 }
157
158 pub fn consents_for_subject(&self, subject: &str) -> Vec<&ConsentRecord> {
160 self.records
161 .iter()
162 .filter(|r| r.subject == subject)
163 .collect()
164 }
165
166 pub fn len(&self) -> usize {
168 self.records.len()
169 }
170
171 pub fn is_empty(&self) -> bool {
173 self.records.is_empty()
174 }
175}
176
177impl Default for ConsentManager {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 fn sample_consent() -> ConsentRecord {
188 ConsentRecord::new(
189 "consent-1",
190 "did:key:z6MkHolder",
191 "did:key:z6MkVerifier",
192 vec![
193 "givenName".into(),
194 "familyName".into(),
195 "dateOfBirth".into(),
196 ],
197 "age-verification",
198 "2026-03-01T00:00:00Z",
199 Some("2026-06-01T00:00:00Z".into()),
200 )
201 }
202
203 #[test]
204 fn new_consent_is_active() {
205 let c = sample_consent();
206 assert_eq!(c.status, ConsentStatus::Active);
207 }
208
209 #[test]
210 fn consent_valid_before_expiry() {
211 let c = sample_consent();
212 assert!(c.is_valid("2026-04-01T00:00:00Z"));
213 }
214
215 #[test]
216 fn consent_invalid_after_expiry() {
217 let c = sample_consent();
218 assert!(!c.is_valid("2026-07-01T00:00:00Z"));
219 }
220
221 #[test]
222 fn consent_invalid_when_revoked() {
223 let mut c = sample_consent();
224 c.revoke();
225 assert_eq!(c.status, ConsentStatus::Revoked);
226 assert!(!c.is_valid("2026-04-01T00:00:00Z"));
227 }
228
229 #[test]
230 fn consent_no_expiry_always_valid() {
231 let c = ConsentRecord::new(
232 "consent-2",
233 "did:key:z6MkHolder",
234 "did:key:z6MkVerifier",
235 vec!["email".into()],
236 "contact",
237 "2026-01-01T00:00:00Z",
238 None,
239 );
240 assert!(c.is_valid("2099-12-31T23:59:59Z"));
241 }
242
243 #[test]
244 fn consent_covers_matching_elements_and_purpose() {
245 let c = sample_consent();
246 assert!(c.covers(&["givenName", "familyName"], "age-verification"));
247 }
248
249 #[test]
250 fn consent_does_not_cover_wrong_purpose() {
251 let c = sample_consent();
252 assert!(!c.covers(&["givenName"], "employment-check"));
253 }
254
255 #[test]
256 fn consent_does_not_cover_missing_elements() {
257 let c = sample_consent();
258 assert!(!c.covers(&["givenName", "socialInsuranceNumber"], "age-verification"));
259 }
260
261 #[test]
262 fn check_expiry_updates_status() {
263 let mut c = sample_consent();
264 c.check_expiry("2026-07-01T00:00:00Z");
265 assert_eq!(c.status, ConsentStatus::Expired);
266 }
267
268 #[test]
269 fn manager_record_and_retrieve() {
270 let mut mgr = ConsentManager::new();
271 assert!(mgr.is_empty());
272
273 mgr.record_consent(sample_consent());
274 assert_eq!(mgr.len(), 1);
275
276 let c = mgr.get_consent("consent-1").unwrap();
277 assert_eq!(c.subject, "did:key:z6MkHolder");
278 }
279
280 #[test]
281 fn manager_revoke_consent() {
282 let mut mgr = ConsentManager::new();
283 mgr.record_consent(sample_consent());
284
285 assert!(mgr.revoke_consent("consent-1"));
286 assert!(!mgr.revoke_consent("nonexistent"));
287
288 let c = mgr.get_consent("consent-1").unwrap();
289 assert_eq!(c.status, ConsentStatus::Revoked);
290 }
291
292 #[test]
293 fn manager_find_valid_consents() {
294 let mut mgr = ConsentManager::new();
295 mgr.record_consent(sample_consent());
296 mgr.record_consent(ConsentRecord::new(
297 "consent-2",
298 "did:key:z6MkHolder",
299 "did:key:z6MkVerifier",
300 vec!["email".into()],
301 "contact",
302 "2026-01-01T00:00:00Z",
303 None,
304 ));
305
306 let valid = mgr.find_valid_consents(
307 "did:key:z6MkVerifier",
308 "age-verification",
309 "2026-04-01T00:00:00Z",
310 );
311 assert_eq!(valid.len(), 1);
312 assert_eq!(valid[0].id, "consent-1");
313 }
314
315 #[test]
316 fn consent_serde_roundtrip() {
317 let c = sample_consent();
318 let json = serde_json::to_string(&c).unwrap();
319 let back: ConsentRecord = serde_json::from_str(&json).unwrap();
320 assert_eq!(back.id, "consent-1");
321 assert_eq!(back.status, ConsentStatus::Active);
322 }
323}