backhopper_core/jigsaw/
releases.rs1use std::slice;
6
7use thiserror::Error;
8
9use crate::model::non_empty::NonEmpty;
10use crate::model::release_version::{HostSeries, HostTag, HostVersion};
11
12pub trait HasHostTag {
13 fn host_tag(&self) -> &HostTag;
14}
15
16impl HasHostTag for HostTag {
17 fn host_tag(&self) -> &HostTag {
18 self
19 }
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Error)]
23pub enum SeriesReleasesError {
24 #[error("no releases")]
25 Empty,
26
27 #[error("release {0} is listed twice")]
28 Duplicate(HostVersion),
29
30 #[error("releases of series {first} and {other} are mixed")]
31 MixedSeries {
32 first: HostSeries,
33 other: HostSeries,
34 },
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct SeriesReleases<R>(NonEmpty<R>);
39
40impl<R: HasHostTag> SeriesReleases<R> {
41 pub fn new(mut releases: Vec<R>) -> Result<Self, SeriesReleasesError> {
42 releases.sort_by(|a, b| a.host_tag().version().cmp(b.host_tag().version()));
43 let releases = NonEmpty::try_from(releases).map_err(|_| SeriesReleasesError::Empty)?;
44 let series = releases.first().host_tag().version().series();
45 for pair in releases.as_slice().windows(2) {
46 let (earlier, later) = (pair[0].host_tag().version(), pair[1].host_tag().version());
47 if earlier == later {
48 return Err(SeriesReleasesError::Duplicate(later.clone()));
49 }
50 }
51 if let Some(other) = releases
52 .iter()
53 .map(|release| release.host_tag().version().series())
54 .find(|other| *other != series)
55 {
56 return Err(SeriesReleasesError::MixedSeries {
57 first: series,
58 other,
59 });
60 }
61 Ok(Self(releases))
62 }
63
64 #[must_use]
65 pub fn build(&self) -> &R {
66 self.0.last()
67 }
68
69 #[must_use]
70 pub fn first(&self) -> &R {
71 self.0.first()
72 }
73
74 #[must_use]
75 pub fn series(&self) -> HostSeries {
76 self.0.first().host_tag().version().series()
77 }
78
79 pub fn iter(&self) -> slice::Iter<'_, R> {
80 self.0.iter()
81 }
82
83 #[must_use]
84 pub fn as_slice(&self) -> &[R] {
85 self.0.as_slice()
86 }
87
88 pub fn map<U: HasHostTag>(
90 self,
91 f: impl FnMut(R) -> U,
92 ) -> Result<SeriesReleases<U>, SeriesReleasesError> {
93 SeriesReleases::new(self.0.into_iter().map(f).collect())
94 }
95}
96
97impl<'a, R> IntoIterator for &'a SeriesReleases<R> {
98 type Item = &'a R;
99 type IntoIter = slice::Iter<'a, R>;
100
101 fn into_iter(self) -> Self::IntoIter {
102 self.0.iter()
103 }
104}