use std::{cmp::Ordering, fmt::Debug, ops::Range};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Step<T, V> {
range: Range<T>,
value: V,
}
#[derive(Debug, Error)]
#[error("empty range: start ({start:?}) >= end ({end:?})")]
pub struct EmptyRangeError<T: Debug> {
pub start: T,
pub end: T,
}
impl<T: Debug + Ord, V> Step<T, V> {
pub fn new(range: Range<T>, value: V) -> Result<Self, EmptyRangeError<T>> {
if range.is_empty() {
Err(EmptyRangeError {
start: range.start,
end: range.end,
})
} else {
Ok(Self { range, value })
}
}
pub fn range(&self) -> &Range<T> {
&self.range
}
pub fn value(&self) -> &V {
&self.value
}
pub fn start(&self) -> &T {
&self.range.start
}
pub fn end(&self) -> &T {
&self.range.end
}
pub fn contains(&self, time: &T) -> bool {
self.range.contains(time)
}
pub fn overlaps(&self, other: &Self) -> bool {
self.range.start < other.range.end && other.range.start < self.range.end
}
pub fn value_at(&self, time: &T) -> Option<&V> {
if self.contains(time) {
Some(&self.value)
} else {
None
}
}
pub fn cmp_to_time(&self, time: &T) -> Ordering {
if self.end() <= time {
Ordering::Less
} else if self.start() > time {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
impl<T: Debug + Ord, V> TryFrom<(Range<T>, V)> for Step<T, V> {
type Error = EmptyRangeError<T>;
fn try_from((range, value): (Range<T>, V)) -> Result<Self, Self::Error> {
Step::new(range, value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn step_creation() {
let step = Step::new(1..5, "a").unwrap();
assert_eq!(step.start(), &1);
assert_eq!(step.end(), &5);
assert_eq!(step.value(), &"a");
let err = Step::new(3..3, "x").unwrap_err();
assert_eq!(err.start, 3);
assert_eq!(err.end, 3);
}
#[test]
fn value_at_works() {
let step = Step::new(10..20, "middle").unwrap();
assert_eq!(step.value_at(&0), None);
assert_eq!(step.value_at(&10), Some(&"middle"));
assert_eq!(step.value_at(&15), Some(&"middle"));
assert_eq!(step.value_at(&20), None);
assert_eq!(step.value_at(&25), None);
}
#[test]
fn overlaps_works() {
let a = Step::new(0..5, "a").unwrap();
let b = Step::new(4..8, "b").unwrap();
let c = Step::new(8..10, "c").unwrap();
assert!(a.overlaps(&b));
assert!(!b.overlaps(&c));
}
#[test]
fn cmp_to_time_works() {
let step = Step::new(10..20, "x").unwrap();
assert_eq!(step.cmp_to_time(&5), std::cmp::Ordering::Greater);
assert_eq!(step.cmp_to_time(&15), std::cmp::Ordering::Equal);
assert_eq!(step.cmp_to_time(&25), std::cmp::Ordering::Less);
}
}