1use core::fmt;
4use core::ops::RangeInclusive;
5use core::str::FromStr;
6use std::collections::HashSet;
7
8use seamantic::sea_orm;
9
10#[derive(
12 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, sea_orm::DeriveValueType,
13)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(transparent))]
16#[repr(transparent)]
17pub struct SeasonNumber(u32);
18
19impl SeasonNumber {
20 pub fn new(value: u32) -> Self {
22 Self(value)
23 }
24}
25
26impl fmt::Display for SeasonNumber {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 self.0.fmt(f)
29 }
30}
31
32impl FromStr for SeasonNumber {
33 type Err = <u32 as FromStr>::Err;
34
35 fn from_str(s: &str) -> Result<Self, Self::Err> {
36 u32::from_str(s).map(Self)
37 }
38}
39
40#[derive(
42 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, sea_orm::DeriveValueType,
43)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45#[cfg_attr(feature = "serde", serde(transparent))]
46#[repr(transparent)]
47pub struct EpisodeNumber(u32);
48
49impl EpisodeNumber {
50 pub fn new(value: u32) -> Self {
52 Self(value)
53 }
54
55 pub fn into_inner(self) -> u32 {
57 self.0
58 }
59}
60
61impl fmt::Display for EpisodeNumber {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 self.0.fmt(f)
64 }
65}
66
67impl FromStr for EpisodeNumber {
68 type Err = <u32 as FromStr>::Err;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 u32::from_str(s).map(Self)
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
77pub enum Error {
78 #[error("zero episodes")]
80 Zero,
81 #[error("noncontiguous episodes")]
83 Noncontiguous,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct EpisodeNumbers(RangeInclusive<EpisodeNumber>);
90
91impl TryFrom<&[EpisodeNumber]> for EpisodeNumbers {
92 type Error = Error;
93
94 fn try_from(value: &[EpisodeNumber]) -> Result<Self, Self::Error> {
95 match value {
96 [] => Err(Error::Zero),
97 [n] => Ok(Self(*n..=*n)),
98 _ => {
99 let min = value.iter().copied().min().unwrap_or_default();
101 let max = value.iter().copied().max().unwrap_or_default();
102 let len = value.len();
103
104 if usize::try_from(max.0.saturating_sub(min.0).saturating_add(1)) != Ok(len) {
105 return Err(Error::Noncontiguous);
106 }
107
108 let set: HashSet<_> = value.iter().copied().collect();
109 if set.len() != len {
110 return Err(Error::Noncontiguous);
111 }
112
113 Ok(Self(min..=max))
114 }
115 }
116 }
117}
118
119impl EpisodeNumbers {
120 pub fn new(start: EpisodeNumber, count: u8) -> Self {
123 Self(start..=EpisodeNumber(start.0.saturating_add(count.into())))
124 }
125
126 pub fn as_range(&self) -> &RangeInclusive<EpisodeNumber> {
128 &self.0
129 }
130
131 pub fn range_string(&self) -> String {
134 let start = self.0.start();
135 let end = self.0.end();
136
137 if start == end {
138 format!("{:02}", start)
139 } else {
140 format!("{:02}-{:02}", start, end)
141 }
142 }
143}