use std::cmp::Ordering;
use std::fmt::Display;
use std::fmt::Write;
use std::ops::Deref;
use thiserror::Error;
use tracing::debug;
use tracing::error;
use crate::records::Check;
use super::{fmt_timestamp, key_value_write, CheckGroup};
#[allow(missing_docs)]
#[derive(Error, Debug, Clone, Copy)]
pub enum SeverityError {
#[error("Ratio of severity out of range: {0}")]
BadRawPercentage(f64),
}
#[allow(missing_docs)]
#[derive(Error, Debug, Clone, Copy)]
pub enum OutageError {
#[error("tried to create an empty outage (without any contained checks)")]
EmptyOutage,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Severity {
Complete,
Partial(f64),
None,
}
impl TryFrom<f64> for Severity {
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value > 1.0 {
return Err(SeverityError::BadRawPercentage(value));
}
Ok(if value == 1.0 {
Severity::Complete
} else if value == 0.0 {
Severity::None
} else {
Severity::Partial(value)
})
}
type Error = SeverityError;
}
impl PartialOrd for Severity {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Self::Complete, Self::Complete) => Some(std::cmp::Ordering::Equal),
(Self::Complete, _) => Some(std::cmp::Ordering::Greater),
(Self::Partial(p1), Self::Partial(p2)) => p1.partial_cmp(p2),
(Self::Partial(_), Self::Complete) => Some(std::cmp::Ordering::Less),
(Self::Partial(_), Self::None) => Some(std::cmp::Ordering::Greater),
(Self::None, Self::None) => Some(std::cmp::Ordering::Equal),
(Self::None, _) => Some(std::cmp::Ordering::Less),
}
}
}
impl Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Complete => write!(f, "Complete")?,
Self::None => write!(f, "No Outage")?,
Self::Partial(p) => write!(f, "Partial ({:.02} %)", p * 100.0)?,
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub struct Outage<'check> {
all: Vec<&'check Check>,
}
impl<'check> Outage<'check> {
pub fn make_outages(all: &[&'check Check]) -> Vec<Outage<'check>> {
let fail_groups = super::fail_groups(all);
debug!("fail groups found: {}", fail_groups.len());
let mut outages: Vec<Outage> = fail_groups
.into_iter()
.map(|a| Outage::try_from(a).expect("check fail group was empty"))
.collect();
outages.sort();
debug!("Outages found: {}", outages.len());
outages
}
pub fn build(all_checks: &[&'check Check]) -> Result<Self, OutageError> {
if all_checks.is_empty() {
error!("tried to create an empty outage");
return Err(OutageError::EmptyOutage);
}
let mut all = all_checks.to_vec();
all.sort();
Ok(Self { all })
}
pub fn all(&self) -> &[&Check] {
&self.all
}
pub fn last(&self) -> Option<&Check> {
self.all.last().copied()
}
pub fn first(&self) -> Option<&Check> {
self.all.first().copied()
}
pub fn short_report(&self) -> Result<String, std::fmt::Error> {
let mut buf: String = String::new();
write!(
&mut buf,
"From {}",
fmt_timestamp(self.first().unwrap().timestamp_parsed()),
)?;
write!(
&mut buf,
" To {}",
fmt_timestamp(self.last().unwrap().timestamp_parsed()),
)?;
write!(&mut buf, ", Total {:>6}", self.len())?;
write!(&mut buf, ", {}", self.severity())?;
Ok(buf)
}
pub fn len(&self) -> usize {
self.all.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn severity(&self) -> Severity {
let all = self.all();
let percentage: f64 =
all.iter().filter(|a| !a.is_success()).count() as f64 / all.len() as f64;
Severity::try_from(percentage).expect("calculated more than 100% success")
}
pub fn cmp_severity(&self, other: &Self) -> Ordering {
match self
.severity()
.partial_cmp(&other.severity())
.unwrap_or(Ordering::Equal)
{
Ordering::Equal => self.len().cmp(&other.len()),
other => other,
}
}
}
impl<'check> TryFrom<&'check [Check]> for Outage<'check> {
type Error = OutageError;
fn try_from(value: &'check [Check]) -> Result<Self, Self::Error> {
let a: Vec<&Check> = value.iter().collect();
Outage::build(&a)
}
}
impl<'check> TryFrom<&'check Vec<&Check>> for Outage<'check> {
type Error = OutageError;
fn try_from(value: &'check Vec<&Check>) -> Result<Self, Self::Error> {
Outage::build(value)
}
}
impl<'check> TryFrom<CheckGroup<'check>> for Outage<'check> {
type Error = OutageError;
fn try_from(value: CheckGroup<'check>) -> Result<Self, Self::Error> {
if value.is_empty() {
return Err(OutageError::EmptyOutage);
}
Outage::build(&value)
}
}
impl<'check> Deref for Outage<'check> {
type Target = Vec<&'check Check>;
fn deref(&self) -> &Self::Target {
&self.all
}
}
impl Display for Outage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf: String = String::new();
key_value_write(
&mut buf,
"From",
fmt_timestamp(self.first().unwrap().timestamp_parsed()),
)?;
key_value_write(
&mut buf,
"To",
fmt_timestamp(self.last().unwrap().timestamp_parsed()),
)?;
key_value_write(&mut buf, "Total", self.len())?;
key_value_write(&mut buf, "Severity", self.severity())?;
writeln!(buf, "\nFirst\n{}", self.last().unwrap())?;
writeln!(buf, "\nLast\n{}", self.last().unwrap())?;
write!(f, "{buf}")?;
Ok(())
}
}