use crate::prelude::*;
use crate::repl::cid::Cid;
use crate::schema::SchemaAttribute;
use crate::valueset::ScimResolveStatus;
use crate::valueset::{DbValueSetV2, ValueSet};
use kanidm_proto::scim_v1::server::ScimAuditString;
use std::collections::BTreeMap;
use time::OffsetDateTime;
type AuditLogStringType = (Cid, String);
pub const AUDIT_LOG_STRING_CAPACITY: usize = 9;
#[derive(Debug, Clone)]
pub struct ValueSetAuditLogString {
map: BTreeMap<Cid, String>,
}
impl ValueSetAuditLogString {
fn remove_oldest(&mut self) {
while self.map.len() > AUDIT_LOG_STRING_CAPACITY {
self.map.pop_first();
}
}
pub fn new((c, s): AuditLogStringType) -> Box<Self> {
let mut map = BTreeMap::new();
map.insert(c, s);
Box::new(ValueSetAuditLogString { map })
}
pub fn from_dbvs2(data: Vec<AuditLogStringType>) -> Result<ValueSet, OperationError> {
let map = data.into_iter().collect();
Ok(Box::new(ValueSetAuditLogString { map }))
}
}
impl ValueSetT for ValueSetAuditLogString {
fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
match value {
Value::AuditLogString(c, s) => {
let r = self.map.insert(c, s);
self.remove_oldest();
Ok(r.is_none())
}
_ => {
debug_assert!(false);
Err(OperationError::InvalidValueState)
}
}
}
fn clear(&mut self) {
self.map.clear();
}
fn remove(&mut self, _pv: &PartialValue, _cid: &Cid) -> bool {
false
}
fn contains(&self, pv: &PartialValue) -> bool {
match pv {
PartialValue::Utf8(s) => self.map.values().any(|current| s.eq(current)),
PartialValue::Cid(c) => self.map.contains_key(c),
_ => {
debug_assert!(false);
true
}
}
}
fn substring(&self, _pv: &PartialValue) -> bool {
false
}
fn startswith(&self, _pv: &PartialValue) -> bool {
false
}
fn endswith(&self, _pv: &PartialValue) -> bool {
false
}
fn lessthan(&self, _pv: &PartialValue) -> bool {
false
}
fn len(&self) -> usize {
self.map.len()
}
fn generate_idx_eq_keys(&self) -> Vec<String> {
self.map.iter().map(|(d, s)| format!("{d}-{s}")).collect()
}
fn syntax(&self) -> SyntaxType {
SyntaxType::AuditLogString
}
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
self.map
.iter()
.all(|(_, s)| Value::validate_str_escapes(s) && Value::validate_singleline(s))
&& self.map.len() <= AUDIT_LOG_STRING_CAPACITY
}
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
Box::new(self.map.iter().map(|(d, s)| format!("{d}-{s}")))
}
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(cid, strdata)| {
let odt: OffsetDateTime = cid.into();
ScimAuditString {
date_time: odt,
value: strdata.clone(),
}
})
.collect::<Vec<_>>(),
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
DbValueSetV2::AuditLogString(
self.map
.iter()
.map(|(c, s)| (c.clone(), s.clone()))
.collect(),
)
}
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
Box::new(self.map.keys().map(|c| PartialValue::Cid(c.clone())))
}
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
Box::new(
self.map
.iter()
.map(|(c, s)| Value::AuditLogString(c.clone(), s.clone())),
)
}
fn equal(&self, other: &ValueSet) -> bool {
if let Some(other) = other.as_audit_log_string() {
&self.map == other
} else {
debug_assert!(false);
false
}
}
fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
if let Some(b) = other.as_audit_log_string() {
mergemaps!(self.map, b)?;
self.remove_oldest();
Ok(())
} else {
debug_assert!(false);
Err(OperationError::InvalidValueState)
}
}
#[allow(clippy::todo)]
fn repl_merge_valueset(&self, older: &ValueSet, _trim_cid: &Cid) -> Option<ValueSet> {
if let Some(mut map) = older.as_audit_log_string().cloned() {
mergemaps!(map, self.map)
.map_err(|_: OperationError| ())
.ok()?;
let mut new_vs = Box::new(ValueSetAuditLogString { map });
new_vs.remove_oldest();
Some(new_vs)
} else {
debug_assert!(false);
None
}
}
fn as_audit_log_string(&self) -> Option<&BTreeMap<Cid, String>> {
Some(&self.map)
}
}
#[cfg(test)]
mod tests {
use super::{ValueSetAuditLogString, AUDIT_LOG_STRING_CAPACITY};
use crate::repl::cid::Cid;
use crate::value::Value;
use crate::valueset::ValueSet;
use std::time::Duration;
#[test]
fn test_valueset_auditlogstring_merge() {
let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(0), "A".to_string()));
assert_eq!(vs.len(), 1);
for i in 1..AUDIT_LOG_STRING_CAPACITY {
vs.insert_checked(Value::AuditLogString(
Cid::new_count(i as u64),
"A".to_string(),
))
.unwrap();
}
assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
vs.insert_checked(Value::AuditLogString(
Cid::new_count(AUDIT_LOG_STRING_CAPACITY as u64),
"A".to_string(),
))
.unwrap();
assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
let mut v_iter = vs.to_value_iter();
let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
unreachable!();
};
assert_eq!(c.ts, Duration::from_secs(1));
println!("{:?}", c);
drop(v_iter);
let other_vs: ValueSet = ValueSetAuditLogString::new(
(Cid::new_count(0), "A".to_string()),
);
assert_eq!(other_vs.len(), 1);
vs.merge(&other_vs)
.expect("Failed to merge, incorrect types");
assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
let mut v_iter = vs.to_value_iter();
let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
unreachable!();
};
assert_eq!(c.ts, Duration::from_secs(1));
println!("{:?}", c);
drop(v_iter);
#[allow(clippy::bool_assert_comparison, clippy::assertions_on_constants)]
{
assert!(100 > AUDIT_LOG_STRING_CAPACITY);
}
let other_vs: ValueSet = ValueSetAuditLogString::new(
(Cid::new_count(100), "A".to_string()),
);
assert_eq!(other_vs.len(), 1);
vs.merge(&other_vs)
.expect("Failed to merge, incorrect types");
assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
let mut v_iter = vs.to_value_iter();
let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
unreachable!();
};
println!("{:?}", c);
assert_eq!(c.ts, Duration::from_secs(2));
drop(v_iter);
}
#[test]
fn test_valueset_auditlogstring_repl_merge() {
let zero_cid = Cid::new_zero();
let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(1), "A".to_string()));
assert_eq!(vs.len(), 1);
for i in 2..(AUDIT_LOG_STRING_CAPACITY + 1) {
vs.insert_checked(Value::AuditLogString(
Cid::new_count(i as u64),
"A".to_string(),
))
.unwrap();
}
assert_eq!(vs.len(), AUDIT_LOG_STRING_CAPACITY);
let other_vs: ValueSet = ValueSetAuditLogString::new(
(Cid::new_count(0), "A".to_string()),
);
assert_eq!(other_vs.len(), 1);
let r_vs = vs
.repl_merge_valueset(&other_vs, &zero_cid)
.expect("merge did not occur");
assert_eq!(r_vs.len(), AUDIT_LOG_STRING_CAPACITY);
let mut v_iter = r_vs.to_value_iter();
let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
unreachable!();
};
assert_eq!(c.ts, Duration::from_secs(1));
println!("{:?}", c);
drop(v_iter);
#[allow(clippy::bool_assert_comparison, clippy::assertions_on_constants)]
{
assert!(100 > AUDIT_LOG_STRING_CAPACITY);
}
let other_vs: ValueSet = ValueSetAuditLogString::new(
(Cid::new_count(100), "A".to_string()),
);
assert_eq!(other_vs.len(), 1);
let r_vs = vs
.repl_merge_valueset(&other_vs, &zero_cid)
.expect("merge did not occur");
assert_eq!(r_vs.len(), AUDIT_LOG_STRING_CAPACITY);
let mut v_iter = r_vs.to_value_iter();
let Some(Value::AuditLogString(c, _s)) = v_iter.next() else {
unreachable!();
};
println!("{:?}", c);
assert_eq!(c.ts, Duration::from_secs(2));
drop(v_iter);
}
#[test]
fn test_scim_auditlog_string() {
let mut vs: ValueSet = ValueSetAuditLogString::new((Cid::new_count(0), "A".to_string()));
assert!(vs.len() == 1);
for i in 1..AUDIT_LOG_STRING_CAPACITY {
vs.insert_checked(Value::AuditLogString(
Cid::new_count(i as u64),
"A".to_string(),
))
.unwrap();
}
let data = r#"
[
{
"dateTime": "1970-01-01T00:00:00Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:01Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:02Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:03Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:04Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:05Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:06Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:07Z",
"value": "A"
},
{
"dateTime": "1970-01-01T00:00:08Z",
"value": "A"
}
]
"#;
crate::valueset::scim_json_reflexive(vs, data);
}
}