use std::time::{Duration, SystemTime};
use crate::audit::ModeratorRationale;
use crate::authority::subjects::ModerationCaseId;
use crate::identity::TraceId;
use crate::proto::Did;
use crate::target::TargetRepresentation;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NotificationId([u8; 16]);
impl NotificationId {
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
NotificationId(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InspectionNotification {
pub notification_id: NotificationId,
pub trace_id: TraceId,
pub kind: InspectionKind,
pub target_repr: TargetRepresentation,
pub at: SystemTime,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InspectionKind {
ModeratorRead {
case: ModerationCaseId,
rationale: ModeratorRationale,
},
Takedown {
case: ModerationCaseId,
rationale: ModeratorRationale,
},
Restore {
case: ModerationCaseId,
rationale: ModeratorRationale,
},
QueueOverflowed {
events_dropped: u32,
},
}
pub trait InspectionNotificationQueueImpl: Send + Sync {
fn enqueue(&self, owner: &Did, event: InspectionNotification);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoInspectionNotifications;
impl InspectionNotificationQueueImpl for NoInspectionNotifications {
fn enqueue(&self, _owner: &Did, _event: InspectionNotification) {
}
}
pub trait InspectionNotificationQueueReader: Send + Sync {
fn read(&self, owner: &Did) -> Vec<InspectionNotification>;
fn acknowledge(&self, owner: &Did, event_ids: &[NotificationId]);
fn retention_window(&self) -> Duration;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::audit::{BoundedString, MAX_RATIONALE_LEN};
fn rationale() -> ModeratorRationale {
ModeratorRationale::Declared(
BoundedString::<MAX_RATIONALE_LEN>::new("test rationale").unwrap(),
)
}
#[test]
fn inspection_kind_v1_variant_set_pinned() {
let case = ModerationCaseId::from_bytes([0u8; 16]);
for k in [
InspectionKind::ModeratorRead {
case,
rationale: rationale(),
},
InspectionKind::Takedown {
case,
rationale: rationale(),
},
InspectionKind::Restore {
case,
rationale: rationale(),
},
InspectionKind::QueueOverflowed { events_dropped: 0 },
] {
match k {
InspectionKind::ModeratorRead { .. }
| InspectionKind::Takedown { .. }
| InspectionKind::Restore { .. }
| InspectionKind::QueueOverflowed { .. } => {}
}
}
}
#[test]
fn no_inspection_notifications_is_callable_as_trait_object() {
let queue: &dyn InspectionNotificationQueueImpl = &NoInspectionNotifications;
let owner = Did::new("did:plc:phase7dtest").unwrap();
let notification = InspectionNotification {
notification_id: NotificationId::from_bytes([0u8; 16]),
trace_id: TraceId::from_bytes([0u8; 16]),
kind: InspectionKind::QueueOverflowed { events_dropped: 0 },
target_repr: TargetRepresentation::structural_only(
crate::StructuralRepresentation::Resource {
did: owner.clone(),
nsid: crate::Nsid::new("tools.kryphocron.feed.postPrivate").unwrap(),
},
),
at: SystemTime::UNIX_EPOCH,
};
queue.enqueue(&owner, notification);
}
#[test]
fn inspection_notification_v1_field_set_pinned() {
let case = ModerationCaseId::from_bytes([0u8; 16]);
let n = InspectionNotification {
notification_id: NotificationId::from_bytes([0u8; 16]),
trace_id: TraceId::from_bytes([0u8; 16]),
kind: InspectionKind::ModeratorRead {
case,
rationale: rationale(),
},
target_repr: TargetRepresentation::structural_only(
crate::StructuralRepresentation::Resource {
did: Did::new("did:plc:phase3test").unwrap(),
nsid: crate::Nsid::new("tools.kryphocron.feed.postPrivate").unwrap(),
},
),
at: SystemTime::UNIX_EPOCH,
};
let InspectionNotification {
notification_id: _,
trace_id: _,
kind: _,
target_repr: _,
at: _,
} = n;
}
}