use crate::error::DoDResult;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct InvariantId(Uuid);
impl InvariantId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl Default for InvariantId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum InvariantSeverity {
Warning,
Error,
Critical,
}
impl InvariantSeverity {
pub fn is_blocking(&self) -> bool {
matches!(self, InvariantSeverity::Error | InvariantSeverity::Critical)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Invariant {
id: InvariantId,
name: String,
predicate: String,
severity: InvariantSeverity,
category: InvariantCategory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum InvariantCategory {
Safety,
Liveness,
Determinism,
Idempotence,
Isolation,
Causality,
Performance,
Governance,
}
impl Invariant {
pub fn new(
name: impl Into<String>, predicate: impl Into<String>, severity: InvariantSeverity,
category: InvariantCategory,
) -> Self {
Self {
id: InvariantId::new(),
name: name.into(),
predicate: predicate.into(),
severity,
category,
}
}
pub fn id(&self) -> InvariantId {
self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn predicate(&self) -> &str {
&self.predicate
}
pub fn severity(&self) -> InvariantSeverity {
self.severity
}
pub fn category(&self) -> InvariantCategory {
self.category
}
pub fn is_blocking(&self) -> bool {
self.severity.is_blocking()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvariantViolation {
invariant_id: InvariantId,
invariant_name: String,
severity: InvariantSeverity,
explanation: String,
context: BTreeMap<String, String>,
}
impl InvariantViolation {
pub fn new(
invariant_id: InvariantId, invariant_name: impl Into<String>, severity: InvariantSeverity,
explanation: impl Into<String>,
) -> Self {
Self {
invariant_id,
invariant_name: invariant_name.into(),
severity,
explanation: explanation.into(),
context: BTreeMap::new(),
}
}
pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.context.insert(key.into(), value.into());
self
}
pub fn severity(&self) -> InvariantSeverity {
self.severity
}
pub fn is_blocking(&self) -> bool {
self.severity.is_blocking()
}
pub fn explanation(&self) -> &str {
&self.explanation
}
}
pub struct InvariantChecker {
invariants: BTreeMap<InvariantId, Invariant>,
}
impl InvariantChecker {
pub fn new() -> Self {
Self {
invariants: BTreeMap::new(),
}
}
pub fn register(mut self, invariant: Invariant) -> Self {
self.invariants.insert(invariant.id(), invariant);
self
}
pub fn register_many(mut self, invariants: Vec<Invariant>) -> Self {
for invariant in invariants {
self.invariants.insert(invariant.id(), invariant);
}
self
}
pub fn all(&self) -> Vec<&Invariant> {
self.invariants.values().collect()
}
pub fn blocking(&self) -> Vec<&Invariant> {
self.invariants
.values()
.filter(|inv| inv.is_blocking())
.collect()
}
pub fn by_category(&self, category: InvariantCategory) -> Vec<&Invariant> {
self.invariants
.values()
.filter(|inv| inv.category() == category)
.collect()
}
pub fn get(&self, id: InvariantId) -> Option<&Invariant> {
self.invariants.get(&id)
}
pub fn check_violations(&self, violations: &[InvariantViolation]) -> DoDResult<()> {
let blocking_violations: Vec<_> = violations.iter().filter(|v| v.is_blocking()).collect();
if !blocking_violations.is_empty() {
let explanation = blocking_violations
.iter()
.map(|v| format!("{}: {}", v.invariant_name, v.explanation))
.collect::<Vec<_>>()
.join("; ");
return Err(crate::error::DoDError::InvariantViolation(explanation));
}
Ok(())
}
}
impl Default for InvariantChecker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invariant_creation() {
let inv = Invariant::new(
"determinism",
"μ(O) must be deterministic",
InvariantSeverity::Critical,
InvariantCategory::Determinism,
);
assert_eq!(inv.name(), "determinism");
assert!(inv.is_blocking());
}
#[test]
fn test_invariant_checker() {
let inv1 = Invariant::new(
"safety",
"no unsafe code",
InvariantSeverity::Critical,
InvariantCategory::Safety,
);
let inv2 = Invariant::new(
"performance",
"τ ≤ 8ms",
InvariantSeverity::Warning,
InvariantCategory::Performance,
);
let checker = InvariantChecker::new().register(inv1).register(inv2);
assert_eq!(checker.all().len(), 2);
assert_eq!(checker.blocking().len(), 1);
}
#[test]
fn test_violation_blocking() {
let violation = InvariantViolation::new(
InvariantId::new(),
"test",
InvariantSeverity::Critical,
"test violation",
);
assert!(violation.is_blocking());
}
#[test]
fn test_violation_checking() {
let checker = InvariantChecker::new();
let blocking = InvariantViolation::new(
InvariantId::new(),
"test",
InvariantSeverity::Critical,
"blocking",
);
let result = checker.check_violations(&[blocking]);
assert!(result.is_err());
}
}