#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)]
pub enum ScanType {
#[default]
Passive,
Event,
IoIntr,
Sec10,
Sec5,
Sec2,
Sec1,
Sec05,
Sec02,
Sec01,
Illegal(u16),
}
impl ScanType {
pub fn from_u16(v: u16) -> Self {
match v {
0 => Self::Passive,
1 => Self::Event,
2 => Self::IoIntr,
3 => Self::Sec10,
4 => Self::Sec5,
5 => Self::Sec2,
6 => Self::Sec1,
7 => Self::Sec05,
8 => Self::Sec02,
9 => Self::Sec01,
other => Self::Illegal(other),
}
}
pub fn to_u16(self) -> u16 {
match self {
Self::Passive => 0,
Self::Event => 1,
Self::IoIntr => 2,
Self::Sec10 => 3,
Self::Sec5 => 4,
Self::Sec2 => 5,
Self::Sec1 => 6,
Self::Sec05 => 7,
Self::Sec02 => 8,
Self::Sec01 => 9,
Self::Illegal(v) => v,
}
}
pub fn scan_list(self) -> Option<ScanList> {
ScanList::of(self)
}
pub fn interval(&self) -> Option<std::time::Duration> {
match self {
Self::Sec10 => Some(std::time::Duration::from_secs(10)),
Self::Sec5 => Some(std::time::Duration::from_secs(5)),
Self::Sec2 => Some(std::time::Duration::from_secs(2)),
Self::Sec1 => Some(std::time::Duration::from_secs(1)),
Self::Sec05 => Some(std::time::Duration::from_millis(500)),
Self::Sec02 => Some(std::time::Duration::from_millis(200)),
Self::Sec01 => Some(std::time::Duration::from_millis(100)),
_ => None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct ScanList(ScanType);
impl ScanList {
pub fn of(scan: ScanType) -> Option<Self> {
match scan {
ScanType::Passive | ScanType::Illegal(_) => None,
scanned => Some(Self(scanned)),
}
}
pub fn scan(self) -> ScanType {
self.0
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct SimModeScan(ScanType);
impl Default for SimModeScan {
fn default() -> Self {
Self(ScanType::Illegal(Self::DO_NOT_USE))
}
}
impl SimModeScan {
pub const DO_NOT_USE: u16 = 65535;
pub fn from_u16(v: u16) -> Self {
Self(ScanType::from_u16(v))
}
pub fn from_scan(s: ScanType) -> Self {
Self(s)
}
pub fn to_u16(self) -> u16 {
self.0.to_u16()
}
pub fn is_unset(self) -> bool {
self.to_u16() == Self::DO_NOT_USE
}
pub fn scan(self) -> Option<ScanType> {
(!self.is_unset()).then_some(self.0)
}
}
impl std::fmt::Display for SimModeScan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Display for ScanType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Passive => write!(f, "Passive"),
Self::Event => write!(f, "Event"),
Self::IoIntr => write!(f, "I/O Intr"),
Self::Sec10 => write!(f, "10 second"),
Self::Sec5 => write!(f, "5 second"),
Self::Sec2 => write!(f, "2 second"),
Self::Sec1 => write!(f, "1 second"),
Self::Sec05 => write!(f, ".5 second"),
Self::Sec02 => write!(f, ".2 second"),
Self::Sec01 => write!(f, ".1 second"),
Self::Illegal(v) => write!(f, "{v}"),
}
}
}
#[cfg(test)]
mod sim_mode_scan_tests {
use super::*;
#[test]
fn default_is_the_65535_sentinel() {
assert!(SimModeScan::default().is_unset());
assert_eq!(SimModeScan::default().to_u16(), 65535);
assert_eq!(SimModeScan::default().scan(), None);
}
#[test]
fn sentinel_round_trips_through_u16() {
assert!(SimModeScan::from_u16(65535).is_unset());
assert_eq!(SimModeScan::from_u16(65535).to_u16(), 65535);
}
#[test]
fn valid_menu_indices_map_to_scan_choices() {
for v in 0u16..=9 {
assert_eq!(SimModeScan::from_u16(v).scan(), Some(ScanType::from_u16(v)));
assert_eq!(SimModeScan::from_u16(v).to_u16(), v);
assert!(!SimModeScan::from_u16(v).is_unset());
}
}
#[test]
fn labels_match_the_shared_menu_table() {
for (i, label) in super::super::menu_choices::MENU_SCAN.iter().enumerate() {
assert_eq!(ScanType::from_u16(i as u16).to_string(), *label);
}
}
#[test]
fn an_out_of_menu_index_is_carried_and_is_not_the_sentinel() {
for v in [10u16, 40000] {
let s = SimModeScan::from_u16(v);
assert_eq!(s.to_u16(), v, "the written index is what reads back");
assert!(!s.is_unset(), "{v} is illegal, but it is not USHRT_MAX");
assert_eq!(
s.scan(),
Some(ScanType::Illegal(v)),
"C swaps it into SCAN; scanAdd then scans nothing"
);
}
}
#[test]
fn scan_carries_an_illegal_index_instead_of_erasing_it_to_passive() {
assert_eq!(ScanType::from_u16(9), ScanType::Sec01);
assert_eq!(ScanType::from_u16(9).to_u16(), 9);
assert_eq!(ScanType::from_u16(10), ScanType::Illegal(10));
assert_eq!(ScanType::from_u16(10).to_u16(), 10);
assert_ne!(ScanType::from_u16(10), ScanType::Passive);
assert_eq!(ScanType::from_u16(65535), ScanType::Illegal(65535));
assert_eq!(ScanType::from_u16(65535).to_u16(), 65535);
assert_eq!(ScanType::Illegal(10).interval(), None);
assert_ne!(ScanType::Illegal(10), ScanType::IoIntr);
}
#[test]
fn only_a_menu_choice_other_than_passive_names_a_scan_list() {
assert_eq!(ScanType::Passive.scan_list(), None);
for v in 1u16..=9 {
let scan = ScanType::from_u16(v);
assert_eq!(
scan.scan_list().map(ScanList::scan),
Some(scan),
"index {v} is a menuScan choice"
);
}
for v in [10u16, 42, 65535] {
assert_eq!(
ScanType::from_u16(v).scan_list(),
None,
"index {v} is outside menuScan; scanAdd adds the record nowhere"
);
}
}
#[test]
fn every_legal_index_round_trips() {
for v in 0u16..=9 {
assert_eq!(ScanType::from_u16(v).to_u16(), v);
}
}
}