1use core::ops::RangeInclusive;
4use std::collections::HashSet;
5
6pub type SeasonNumber = u32;
8pub type EpisodeNumber = u32;
10
11#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error("zero episodes")]
16 Zero,
17 #[error("noncontiguous episodes")]
19 Noncontiguous,
20}
21
22#[derive(Debug)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct EpisodeNumbers(RangeInclusive<EpisodeNumber>);
26
27impl TryFrom<&[EpisodeNumber]> for EpisodeNumbers {
28 type Error = Error;
29
30 fn try_from(value: &[EpisodeNumber]) -> Result<Self, Self::Error> {
31 match value {
32 [] => Err(Error::Zero),
33 [n] => Ok(Self(*n..=*n)),
34 _ => {
35 let min = value.iter().copied().min().unwrap_or_default();
37 let max = value.iter().copied().max().unwrap_or_default();
38 let len = value.len();
39
40 if usize::try_from(max.saturating_sub(min).saturating_add(1)) != Ok(len) {
41 return Err(Error::Noncontiguous);
42 }
43
44 let set: HashSet<_> = value.iter().copied().collect();
45 if set.len() != len {
46 return Err(Error::Noncontiguous);
47 }
48
49 Ok(Self(min..=max))
50 }
51 }
52 }
53}
54
55impl EpisodeNumbers {
56 pub fn as_range(&self) -> &RangeInclusive<EpisodeNumber> {
58 &self.0
59 }
60}