1use crate::prelude::*;
2use crate::repl::cid::Cid;
3use crate::schema::SchemaAttribute;
4use crate::valueset::ScimResolveStatus;
5use crate::valueset::{DbValueSetV2, ValueSet};
6use kanidm_proto::scim_v1::server::ScimAuditString;
7use std::collections::BTreeMap;
8use time::OffsetDateTime;
9
10type AuditLogStringType = (Cid, String);
11
12pub const AUDIT_LOG_STRING_CAPACITY: usize = 9;
13
14#[derive(Debug, Clone)]
15pub struct ValueSetAuditLogString {
16 map: BTreeMap<Cid, String>,
17}
18
19impl ValueSetAuditLogString {
20 fn remove_oldest(&mut self) {
21 while self.map.len() > AUDIT_LOG_STRING_CAPACITY {
23 self.map.pop_first();
24 }
25 }
26
27 pub fn new((c, s): AuditLogStringType) -> Box<Self> {
28 let mut map = BTreeMap::new();
29 map.insert(c, s);
30 Box::new(ValueSetAuditLogString { map })
31 }
32
33 pub fn from_dbvs2(data: Vec<AuditLogStringType>) -> Result<ValueSet, OperationError> {
34 let map = data.into_iter().collect();
35 Ok(Box::new(ValueSetAuditLogString { map }))
36 }
37}
38
39impl ValueSetT for ValueSetAuditLogString {
40 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
41 match value {
42 Value::AuditLogString(c, s) => {
43 let r = self.map.insert(c, s);
44 self.remove_oldest();
45 Ok(r.is_none())
47 }
48 _ => {
49 debug_assert!(false);
50 Err(OperationError::InvalidValueState)
51 }
52 }
53 }
54
55 fn clear(&mut self) {
56 self.map.clear();
57 }
58
59 fn remove(&mut self, _pv: &PartialValue, _cid: &Cid) -> bool {
60 false
61 }
62
63 fn contains(&self, pv: &PartialValue) -> bool {
64 match pv {
65 PartialValue::Utf8(s) => self.map.values().any(|current| s.eq(current)),
66 PartialValue::Cid(c) => self.map.contains_key(c),
67 _ => {
68 debug_assert!(false);
69 true
70 }
71 }
72 }
73
74 fn substring(&self, _pv: &PartialValue) -> bool {
75 false
76 }
77
78 fn startswith(&self, _pv: &PartialValue) -> bool {
79 false
80 }
81
82 fn endswith(&self, _pv: &PartialValue) -> bool {
83 false
84 }
85
86 fn lessthan(&self, _pv: &PartialValue) -> bool {
87 false
88 }
89
90 fn len(&self) -> usize {
91 self.map.len()
92 }
93
94 fn generate_idx_eq_keys(&self) -> Vec<String> {
95 self.map.iter().map(|(d, s)| format!("{d}-{s}")).collect()
96 }
97
98 fn syntax(&self) -> SyntaxType {
99 SyntaxType::AuditLogString
100 }
101
102 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
103 self.map
104 .iter()
105 .all(|(_, s)| Value::validate_str_escapes(s) && Value::validate_singleline(s))
106 && self.map.len() <= AUDIT_LOG_STRING_CAPACITY
107 }
108
109 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
110 Box::new(self.map.iter().map(|(d, s)| format!("{d}-{s}")))
111 }
112
113 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
114 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
115 self.map
116 .iter()
117 .map(|(cid, strdata)| {
118 let odt: OffsetDateTime = cid.into();
119 ScimAuditString {
120 date_time: odt,
121 value: strdata.clone(),
122 }
123 })
124 .collect::<Vec<_>>(),
125 )))
126 }
127
128 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
129 DbValueSetV2::AuditLogString(
130 self.map
131 .iter()
132 .map(|(c, s)| (c.clone(), s.clone()))
133 .collect(),
134 )
135 }
136
137 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
138 Box::new(self.map.keys().map(|c| PartialValue::Cid(c.clone())))
139 }
140
141 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
142 Box::new(
143 self.map
144 .iter()
145 .map(|(c, s)| Value::AuditLogString(c.clone(), s.clone())),
146 )
147 }
148
149 fn equal(&self, other: &ValueSet) -> bool {
150 if let Some(other) = other.as_audit_log_string() {
151 &self.map == other
152 } else {
153 debug_assert!(false);
154 false
155 }
156 }
157
158 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
159 if let Some(b) = other.as_audit_log_string() {
160 mergemaps!(self.map, b)?;
161 self.remove_oldest();
162 Ok(())
163 } else {
164 debug_assert!(false);
165 Err(OperationError::InvalidValueState)
166 }
167 }
168
169 #[allow(clippy::todo)]
170 fn repl_merge_valueset(&self, older: &ValueSet, _trim_cid: &Cid) -> Option<ValueSet> {
171 if let Some(mut map) = older.as_audit_log_string().cloned() {
172 mergemaps!(map, self.map)
175 .map_err(|_: OperationError| ())
176 .ok()?;
177 let mut new_vs = Box::new(ValueSetAuditLogString { map });
178 new_vs.remove_oldest();
179 Some(new_vs)
180 } else {
181 debug_assert!(false);
182 None
183 }
184 }
185
186 fn as_audit_log_string(&self) -> Option<&BTreeMap<Cid, String>> {
187 Some(&self.map)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::{ValueSetAuditLogString, AUDIT_LOG_STRING_CAPACITY};
194 use crate::repl::cid::Cid;
195 use crate::value::Value;
196 use crate::valueset::ValueSet;
197 use std::time::Duration;
198
199 #[test]
200 fn test_valueset_auditlogstring_merge() {
201 let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(0), "A".to_string()));
202 assert_eq!(vs.len(), 1);
203
204 for i in 1..AUDIT_LOG_STRING_CAPACITY {
205 vs.insert_checked(Value::AuditLogString(
206 Cid::new_count(i as u64),
207 "A".to_string(),
208 ))
209 .unwrap();
210 }
211
212 assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
213
214 vs.insert_checked(Value::AuditLogString(
216 Cid::new_count(AUDIT_LOG_STRING_CAPACITY as u64),
217 "A".to_string(),
218 ))
219 .unwrap();
220
221 assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
222
223 let mut v_iter = vs.to_value_iter();
224 let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
225 unreachable!();
226 };
227 assert_eq!(c.ts, Duration::from_secs(1));
229 println!("{:?}", c);
230 drop(v_iter);
231
232 let other_vs: ValueSet = ValueSetAuditLogString::new(
234 (Cid::new_count(0), "A".to_string()),
236 );
237 assert_eq!(other_vs.len(), 1);
238
239 vs.merge(&other_vs)
241 .expect("Failed to merge, incorrect types");
242
243 assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
245 let mut v_iter = vs.to_value_iter();
246 let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
247 unreachable!();
248 };
249 assert_eq!(c.ts, Duration::from_secs(1));
251 println!("{:?}", c);
252 drop(v_iter);
253
254 #[allow(clippy::bool_assert_comparison, clippy::assertions_on_constants)]
257 {
258 assert!(100 > AUDIT_LOG_STRING_CAPACITY);
259 }
260
261 let other_vs: ValueSet = ValueSetAuditLogString::new(
262 (Cid::new_count(100), "A".to_string()),
264 );
265 assert_eq!(other_vs.len(), 1);
266
267 vs.merge(&other_vs)
268 .expect("Failed to merge, incorrect types");
269
270 assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
272 let mut v_iter = vs.to_value_iter();
273 let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
274 unreachable!();
275 };
276 println!("{:?}", c);
278 assert_eq!(c.ts, Duration::from_secs(2));
279 drop(v_iter);
280 }
281
282 #[test]
283 fn test_valueset_auditlogstring_repl_merge() {
284 let zero_cid = Cid::new_zero();
285 let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(1), "A".to_string()));
286 assert_eq!(vs.len(), 1);
287
288 for i in 2..(AUDIT_LOG_STRING_CAPACITY + 1) {
289 vs.insert_checked(Value::AuditLogString(
290 Cid::new_count(i as u64),
291 "A".to_string(),
292 ))
293 .unwrap();
294 }
295
296 assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
297
298 let other_vs: ValueSet = ValueSetAuditLogString::new(
300 (Cid::new_count(0), "A".to_string()),
302 );
303 assert_eq!(other_vs.len(), 1);
304
305 let r_vs = vs
307 .repl_merge_valueset(&other_vs, &zero_cid)
308 .expect("merge did not occur");
309
310 assert_eq!(r_vs.len(), AUDIT_LOG_STRING_CAPACITY);
312 let mut v_iter = r_vs.to_value_iter();
313 let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
314 unreachable!();
315 };
316 assert_eq!(c.ts, Duration::from_secs(1));
318 println!("{:?}", c);
319 drop(v_iter);
320
321 #[allow(clippy::bool_assert_comparison, clippy::assertions_on_constants)]
324 {
325 assert!(100 > AUDIT_LOG_STRING_CAPACITY);
326 }
327
328 let other_vs: ValueSet = ValueSetAuditLogString::new(
329 (Cid::new_count(100), "A".to_string()),
331 );
332 assert_eq!(other_vs.len(), 1);
333
334 let r_vs = vs
335 .repl_merge_valueset(&other_vs, &zero_cid)
336 .expect("merge did not occur");
337
338 assert_eq!(r_vs.len(), AUDIT_LOG_STRING_CAPACITY);
340 let mut v_iter = r_vs.to_value_iter();
341 let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
342 unreachable!();
343 };
344 println!("{:?}", c);
346 assert_eq!(c.ts, Duration::from_secs(2));
347 drop(v_iter);
348 }
349
350 #[test]
351 fn test_scim_auditlog_string() {
352 let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(0), "A".to_string()));
353 assert!(vs.len() == 1);
354
355 for i in 1..AUDIT_LOG_STRING_CAPACITY {
356 vs.insert_checked(Value::AuditLogString(
357 Cid::new_count(i as u64),
358 "A".to_string(),
359 ))
360 .unwrap();
361 }
362
363 let data = r#"
364[
365 {
366 "dateTime": "1970-01-01T00:00:00Z",
367 "value": "A"
368 },
369 {
370 "dateTime": "1970-01-01T00:00:01Z",
371 "value": "A"
372 },
373 {
374 "dateTime": "1970-01-01T00:00:02Z",
375 "value": "A"
376 },
377 {
378 "dateTime": "1970-01-01T00:00:03Z",
379 "value": "A"
380 },
381 {
382 "dateTime": "1970-01-01T00:00:04Z",
383 "value": "A"
384 },
385 {
386 "dateTime": "1970-01-01T00:00:05Z",
387 "value": "A"
388 },
389 {
390 "dateTime": "1970-01-01T00:00:06Z",
391 "value": "A"
392 },
393 {
394 "dateTime": "1970-01-01T00:00:07Z",
395 "value": "A"
396 },
397 {
398 "dateTime": "1970-01-01T00:00:08Z",
399 "value": "A"
400 }
401]
402"#;
403 crate::valueset::scim_json_reflexive(&vs, data);
404 }
405}