use anyhow::anyhow;
use itertools::Itertools;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TuiColumns(pub Vec<TuiColumn>);
impl TryFrom<&str> for TuiColumns {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self(
value
.chars()
.map(TuiColumn::try_from)
.collect::<Result<Vec<_>, Self::Error>>()?,
))
}
}
impl Default for TuiColumns {
fn default() -> Self {
Self::try_from(super::constants::DEFAULT_CUSTOM_COLUMNS).expect("custom columns")
}
}
impl TuiColumns {
pub fn find_duplicates(&self) -> Vec<String> {
let (_, duplicates) = self.0.iter().fold(
(HashSet::<TuiColumn>::new(), Vec::new()),
|(mut all, mut dups), column| {
if all.iter().contains(column) {
dups.push(column.to_string());
} else {
all.insert(*column);
}
(all, dups)
},
);
duplicates
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum TuiColumn {
Ttl,
Host,
LossPct,
Sent,
Received,
Last,
Average,
Best,
Worst,
StdDev,
Status,
Jitter,
Javg,
Jmax,
Jinta,
LastSrcPort,
LastDestPort,
LastSeq,
LastIcmpPacketType,
LastIcmpPacketCode,
LastNatStatus,
Failed,
Floss,
Bloss,
FlossPct,
Dscp,
Ecn,
}
impl TryFrom<char> for TuiColumn {
type Error = anyhow::Error;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'h' => Ok(Self::Ttl),
'o' => Ok(Self::Host),
'l' => Ok(Self::LossPct),
's' => Ok(Self::Sent),
'r' => Ok(Self::Received),
'a' => Ok(Self::Last),
'v' => Ok(Self::Average),
'b' => Ok(Self::Best),
'w' => Ok(Self::Worst),
'd' => Ok(Self::StdDev),
't' => Ok(Self::Status),
'j' => Ok(Self::Jitter),
'g' => Ok(Self::Javg),
'x' => Ok(Self::Jmax),
'i' => Ok(Self::Jinta),
'S' => Ok(Self::LastSrcPort),
'P' => Ok(Self::LastDestPort),
'Q' => Ok(Self::LastSeq),
'T' => Ok(Self::LastIcmpPacketType),
'C' => Ok(Self::LastIcmpPacketCode),
'N' => Ok(Self::LastNatStatus),
'f' => Ok(Self::Failed),
'F' => Ok(Self::Floss),
'B' => Ok(Self::Bloss),
'D' => Ok(Self::FlossPct),
'K' => Ok(Self::Dscp),
'M' => Ok(Self::Ecn),
c => Err(anyhow!(format!("unknown column code: {c}"))),
}
}
}
impl Display for TuiColumn {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ttl => write!(f, "h"),
Self::Host => write!(f, "o"),
Self::LossPct => write!(f, "l"),
Self::Sent => write!(f, "s"),
Self::Received => write!(f, "r"),
Self::Last => write!(f, "a"),
Self::Average => write!(f, "v"),
Self::Best => write!(f, "b"),
Self::Worst => write!(f, "w"),
Self::StdDev => write!(f, "d"),
Self::Status => write!(f, "t"),
Self::Jitter => write!(f, "j"),
Self::Javg => write!(f, "g"),
Self::Jmax => write!(f, "x"),
Self::Jinta => write!(f, "i"),
Self::LastSrcPort => write!(f, "S"),
Self::LastDestPort => write!(f, "P"),
Self::LastSeq => write!(f, "Q"),
Self::LastIcmpPacketType => write!(f, "T"),
Self::LastIcmpPacketCode => write!(f, "C"),
Self::LastNatStatus => write!(f, "N"),
Self::Failed => write!(f, "f"),
Self::Floss => write!(f, "F"),
Self::Bloss => write!(f, "B"),
Self::FlossPct => write!(f, "D"),
Self::Dscp => write!(f, "K"),
Self::Ecn => write!(f, "M"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case('h', TuiColumn::Ttl)]
#[test_case('o', TuiColumn::Host)]
#[test_case('l', TuiColumn::LossPct)]
#[test_case('s', TuiColumn::Sent)]
#[test_case('r', TuiColumn::Received)]
#[test_case('a', TuiColumn::Last)]
#[test_case('v', TuiColumn::Average)]
#[test_case('b', TuiColumn::Best)]
#[test_case('w', TuiColumn::Worst)]
#[test_case('d', TuiColumn::StdDev)]
#[test_case('t', TuiColumn::Status)]
fn test_try_from_char_for_tui_column(c: char, t: TuiColumn) {
assert_eq!(TuiColumn::try_from(c).unwrap(), t);
}
#[test_case('k' ; "invalid k")]
#[test_case('z' ; "invalid z")]
fn test_try_invalid_char_for_tui_column(c: char) {
assert!(TuiColumn::try_from(c).is_err());
}
#[test_case(TuiColumn::Ttl, "h")]
#[test_case(TuiColumn::Host, "o")]
#[test_case(TuiColumn::LossPct, "l")]
#[test_case(TuiColumn::Sent, "s")]
#[test_case(TuiColumn::Received, "r")]
#[test_case(TuiColumn::Last, "a")]
#[test_case(TuiColumn::Average, "v")]
#[test_case(TuiColumn::Best, "b")]
#[test_case(TuiColumn::Worst, "w")]
#[test_case(TuiColumn::StdDev, "d")]
#[test_case(TuiColumn::Status, "t")]
fn test_display_formatting_for_tui_column(t: TuiColumn, letter: &'static str) {
assert_eq!(format!("{t}"), letter);
}
#[test]
fn test_try_from_str_for_tui_columns() {
let valid_input = "hol";
let tui_columns = TuiColumns::try_from(valid_input).unwrap();
assert_eq!(
tui_columns,
TuiColumns(vec![TuiColumn::Ttl, TuiColumn::Host, TuiColumn::LossPct])
);
let invalid_input = "xyz";
assert!(TuiColumns::try_from(invalid_input).is_err());
}
#[test]
fn test_default_for_tui_columns() {
let default_columns = TuiColumns::default();
assert_eq!(
default_columns,
TuiColumns(vec![
TuiColumn::Ttl,
TuiColumn::Host,
TuiColumn::LossPct,
TuiColumn::Sent,
TuiColumn::Received,
TuiColumn::Last,
TuiColumn::Average,
TuiColumn::Best,
TuiColumn::Worst,
TuiColumn::StdDev,
TuiColumn::Status
])
);
}
#[test]
fn test_find_duplicates_for_tui_columns() {
let columns_with_duplicates = TuiColumns(vec![
TuiColumn::Ttl,
TuiColumn::Host,
TuiColumn::LossPct,
TuiColumn::Host, ]);
let duplicates = columns_with_duplicates.find_duplicates();
assert_eq!(duplicates, vec!["o".to_string()]);
}
}