use std::collections::HashSet;
use serde::Serialize;
use crate::event::Event;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct LogProblem {
pub line: usize,
pub kind: LogProblemKind,
pub detail: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum LogProblemKind {
MissingSessionOpened,
DuplicateSessionOpened,
DuplicateFailureModeId,
DuplicateMitigationId,
UnknownFailureMode,
UnknownMitigation,
SessionIdMismatch,
}
pub fn validate_log(session_id: &str, events: &[Event]) -> Vec<LogProblem> {
let mut problems = Vec::new();
let mut push = |line: usize, kind: LogProblemKind, detail: String| {
problems.push(LogProblem { line, kind, detail });
};
if !matches!(events.first(), Some(Event::SessionOpened { .. })) {
push(
usize::from(!events.is_empty()),
LogProblemKind::MissingSessionOpened,
"the first event is not session_opened".to_string(),
);
}
let mut ids: HashSet<&str> = HashSet::new();
let mut failure_modes: HashSet<&str> = HashSet::new();
let mut mitigations: HashSet<&str> = HashSet::new();
let mut session_opened_seen = false;
for (idx, event) in events.iter().enumerate() {
let line = idx + 1;
if event.session_id() != session_id {
push(
line,
LogProblemKind::SessionIdMismatch,
format!(
"event names session '{}', expected '{session_id}'",
event.session_id()
),
);
}
match event {
Event::SessionOpened { .. } => {
if session_opened_seen {
push(
line,
LogProblemKind::DuplicateSessionOpened,
"session_opened appears again after the first one".to_string(),
);
}
session_opened_seen = true;
}
Event::FailureModeAdded { failure_mode } => {
let id = failure_mode.id.as_str();
if !ids.insert(id) {
push(
line,
LogProblemKind::DuplicateFailureModeId,
format!("failure mode id '{id}' was already used"),
);
}
failure_modes.insert(id);
}
Event::MitigationAdded { mitigation } => {
let id = mitigation.id.as_str();
if !ids.insert(id) {
push(
line,
LogProblemKind::DuplicateMitigationId,
format!("mitigation id '{id}' was already used"),
);
}
let fm = mitigation.failure_mode_id.as_str();
if !failure_modes.contains(fm) {
push(
line,
LogProblemKind::UnknownFailureMode,
format!("mitigation '{id}' names unknown failure mode '{fm}'"),
);
}
mitigations.insert(id);
}
Event::Rescored { rescore } => {
let fm = rescore.failure_mode_id.as_str();
if !failure_modes.contains(fm) {
push(
line,
LogProblemKind::UnknownFailureMode,
format!("rescore names unknown failure mode '{fm}'"),
);
}
}
Event::MitigationStatusChanged { change } => {
let id = change.mitigation_id.as_str();
if !mitigations.contains(id) {
push(
line,
LogProblemKind::UnknownMitigation,
format!("status change names unknown mitigation '{id}'"),
);
}
}
Event::FailureModeDispositioned { change } => {
let fm = change.failure_mode_id.as_str();
if !failure_modes.contains(fm) {
push(
line,
LogProblemKind::UnknownFailureMode,
format!("disposition names unknown failure mode '{fm}'"),
);
}
}
}
}
problems
}