assorted_debian_utils/
archive.rs

1// Copyright 2022-2024 Sebastian Ramacher
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! # Helpers to handle Debian archives
5//!
6//! These helpers includes enums to handle suites, codenames, and other fields found in Debian archive files.
7
8use std::fmt::{Display, Formatter};
9use std::str::FromStr;
10
11use serde::{Deserialize, Serialize, Serializer};
12
13use crate::utils::TryFromStrVisitor;
14pub use crate::ParseError;
15
16/// "Extensions" to a codename or a suite
17///
18/// This enum covers the archives for backports, security updates, (old)stable
19/// updates and (old)stable proposed-updates.
20#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
21pub enum Extension {
22    /// The backports extension
23    Backports,
24    /// The security extension
25    Security,
26    /// The updates extension
27    Updates,
28    /// The proposed-upates extension
29    ProposedUpdates,
30}
31
32impl AsRef<str> for Extension {
33    fn as_ref(&self) -> &str {
34        match self {
35            Self::Backports => "backports",
36            Self::Security => "security",
37            Self::Updates => "updates",
38            Self::ProposedUpdates => "proposed-updates",
39        }
40    }
41}
42
43impl Display for Extension {
44    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45        write!(f, "{}", self.as_ref())
46    }
47}
48
49impl TryFrom<&str> for Extension {
50    type Error = ParseError;
51
52    fn try_from(value: &str) -> Result<Self, Self::Error> {
53        match value {
54            "backports" => Ok(Self::Backports),
55            "security" => Ok(Self::Security),
56            "updates" => Ok(Self::Updates),
57            "proposed-updates" => Ok(Self::ProposedUpdates),
58            _ => Err(ParseError::InvalidExtension),
59        }
60    }
61}
62
63impl FromStr for Extension {
64    type Err = ParseError;
65
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        Self::try_from(s)
68    }
69}
70
71/// Debian archive suites
72///
73/// This enum describes the suite names found in the Debian archive.
74#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
75pub enum Suite {
76    /// The unstable suite
77    Unstable,
78    /// The testing suite
79    Testing(Option<Extension>),
80    /// The stable suite
81    Stable(Option<Extension>),
82    /// The oldstable suite
83    OldStable(Option<Extension>),
84    /// The experimental suite
85    Experimental,
86}
87
88impl Suite {
89    /// Extend suite with an extension archive.
90    ///
91    /// An existing extension will overriden and the method has no effect for`unstable` and `experimental`.
92    pub fn with_extension(&self, extension: Extension) -> Self {
93        match self {
94            Self::Unstable | Self::Experimental => *self,
95            Self::Testing(_) => Self::Testing(Some(extension)),
96            Self::Stable(_) => Self::Stable(Some(extension)),
97            Self::OldStable(_) => Self::OldStable(Some(extension)),
98        }
99    }
100
101    /// Remove an extension archive from the suite.
102    ///
103    /// The method has no effect for`unstable` and `experimental`.
104    pub fn without_extension(&self) -> Self {
105        match self {
106            Self::Unstable | Self::Experimental => *self,
107            Self::Testing(_) => Self::Testing(None),
108            Self::Stable(_) => Self::Stable(None),
109            Self::OldStable(_) => Self::OldStable(None),
110        }
111    }
112}
113
114impl Display for Suite {
115    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
116        match self {
117            Self::Unstable => write!(f, "unstable"),
118            Self::Testing(None) => write!(f, "testing"),
119            Self::Stable(None) => write!(f, "stable"),
120            Self::OldStable(None) => write!(f, "oldstable"),
121            Self::Experimental => write!(f, "experimental"),
122            Self::Testing(Some(ext)) => write!(f, "testing-{ext}"),
123            Self::Stable(Some(ext)) => write!(f, "stable-{ext}"),
124            Self::OldStable(Some(ext)) => write!(f, "oldstable-{ext}"),
125        }
126    }
127}
128
129impl TryFrom<&str> for Suite {
130    type Error = ParseError;
131
132    fn try_from(value: &str) -> Result<Self, Self::Error> {
133        match value {
134            "unstable" => Ok(Self::Unstable),
135            "testing" => Ok(Self::Testing(None)),
136            "stable" => Ok(Self::Stable(None)),
137            "oldstable" => Ok(Self::OldStable(None)),
138            // The Release file from stable-proposed-updates calls the suite proposed-updaptes.
139            "proposed-updates" => Ok(Self::Stable(Some(Extension::ProposedUpdates))),
140            "experimental" => Ok(Self::Experimental),
141            _ => {
142                let s = value.split_once('-').ok_or(ParseError::InvalidSuite)?;
143                let ext = Extension::try_from(s.1)?;
144                match s.0 {
145                    "testing" => Ok(Self::Testing(Some(ext))),
146                    "stable" => Ok(Self::Stable(Some(ext))),
147                    "oldstable" => Ok(Self::OldStable(Some(ext))),
148                    _ => Err(ParseError::InvalidSuite),
149                }
150            }
151        }
152    }
153}
154
155impl FromStr for Suite {
156    type Err = ParseError;
157
158    fn from_str(s: &str) -> Result<Self, Self::Err> {
159        Self::try_from(s)
160    }
161}
162
163impl Serialize for Suite {
164    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
165    where
166        S: Serializer,
167    {
168        serializer.serialize_str(&self.to_string())
169    }
170}
171
172impl<'de> Deserialize<'de> for Suite {
173    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174    where
175        D: serde::Deserializer<'de>,
176    {
177        deserializer.deserialize_str(TryFromStrVisitor::<Self>::new("a suite name"))
178    }
179}
180
181/// Debian archive codenames
182///
183/// This enum describes the codenames names found in the Debian archive.
184#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
185pub enum Codename {
186    /// The unstable suite
187    Sid,
188    /// The testing suite
189    Trixie(Option<Extension>),
190    /// The stable suite
191    Bookworm(Option<Extension>),
192    /// The oldstable suite
193    Bullseye(Option<Extension>),
194    /// The experimental suite
195    RCBuggy,
196}
197
198impl Display for Codename {
199    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
200        match self {
201            Self::Sid => write!(f, "sid"),
202            Self::Trixie(None) => write!(f, "trixie"),
203            Self::Bookworm(None) => write!(f, "bookworm"),
204            Self::Bullseye(None) => write!(f, "bullseye"),
205            Self::RCBuggy => write!(f, "rc-buggy"),
206            Self::Trixie(Some(ext)) => write!(f, "trixie-{ext}"),
207            Self::Bookworm(Some(ext)) => write!(f, "bookworm-{ext}"),
208            Self::Bullseye(Some(ext)) => write!(f, "bullseye-{ext}"),
209        }
210    }
211}
212
213impl TryFrom<&str> for Codename {
214    type Error = ParseError;
215
216    fn try_from(value: &str) -> Result<Self, Self::Error> {
217        match value {
218            "sid" => Ok(Self::Sid),
219            "trixie" => Ok(Self::Trixie(None)),
220            "bookworm" => Ok(Self::Bookworm(None)),
221            "bullseye" => Ok(Self::Bullseye(None)),
222            "rc-buggy" => Ok(Self::RCBuggy),
223            _ => {
224                let s = value.split_once('-').ok_or(ParseError::InvalidCodename)?;
225                let ext = Extension::try_from(s.1)?;
226                match s.0 {
227                    "trixie" => Ok(Self::Trixie(Some(ext))),
228                    "bookworm" => Ok(Self::Bookworm(Some(ext))),
229                    "bullseye" => Ok(Self::Bullseye(Some(ext))),
230                    _ => Err(ParseError::InvalidCodename),
231                }
232            }
233        }
234    }
235}
236
237impl FromStr for Codename {
238    type Err = ParseError;
239
240    fn from_str(s: &str) -> Result<Self, Self::Err> {
241        Self::try_from(s)
242    }
243}
244
245impl From<Suite> for Codename {
246    fn from(suite: Suite) -> Self {
247        match suite {
248            Suite::Unstable => Self::Sid,
249            Suite::Testing(ext) => Self::Trixie(ext),
250            Suite::Stable(ext) => Self::Bookworm(ext),
251            Suite::OldStable(ext) => Self::Bullseye(ext),
252            Suite::Experimental => Self::RCBuggy,
253        }
254    }
255}
256
257impl From<Codename> for Suite {
258    fn from(codename: Codename) -> Self {
259        match codename {
260            Codename::Sid => Self::Unstable,
261            Codename::Trixie(ext) => Self::Testing(ext),
262            Codename::Bookworm(ext) => Self::Stable(ext),
263            Codename::Bullseye(ext) => Self::OldStable(ext),
264            Codename::RCBuggy => Self::Experimental,
265        }
266    }
267}
268
269impl Serialize for Codename {
270    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
271    where
272        S: Serializer,
273    {
274        serializer.serialize_str(&self.to_string())
275    }
276}
277
278impl<'de> Deserialize<'de> for Codename {
279    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280    where
281        D: serde::Deserializer<'de>,
282    {
283        deserializer.deserialize_str(TryFromStrVisitor::<Self>::new("a codename"))
284    }
285}
286
287/// Represents either a suite or codename
288///
289/// This enum is useful whenever a suite name or codename works
290#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
291pub enum SuiteOrCodename {
292    /// A suite
293    Suite(Suite),
294    /// A codename
295    Codename(Codename),
296}
297
298impl SuiteOrCodename {
299    /// Unstable
300    pub const UNSTABLE: Self = Self::Suite(Suite::Unstable);
301    /// Testing
302    pub const TESTING: Self = Self::Suite(Suite::Testing(None));
303    /// Stable
304    pub const STABLE: Self = Self::Suite(Suite::Stable(None));
305    /// Oldstable
306    pub const OLDSTABLE: Self = Self::Suite(Suite::OldStable(None));
307    /// Experimental
308    pub const EXPERIMENTAL: Self = Self::Suite(Suite::Experimental);
309    /// Stable proposed-updates
310    pub const STABLE_PU: Self = Self::Suite(Suite::Stable(Some(Extension::ProposedUpdates)));
311    /// Oldstable propoused-updates
312    pub const OLDSTABLE_PU: Self = Self::Suite(Suite::OldStable(Some(Extension::ProposedUpdates)));
313    /// Stable backports
314    pub const STABLE_BACKPORTS: Self = Self::Suite(Suite::Stable(Some(Extension::Backports)));
315}
316
317impl From<Codename> for SuiteOrCodename {
318    fn from(codename: Codename) -> Self {
319        Self::Codename(codename)
320    }
321}
322
323impl From<Suite> for SuiteOrCodename {
324    fn from(suite: Suite) -> Self {
325        Self::Suite(suite)
326    }
327}
328
329impl TryFrom<&str> for SuiteOrCodename {
330    type Error = ParseError;
331
332    fn try_from(value: &str) -> Result<Self, Self::Error> {
333        match Suite::try_from(value) {
334            Ok(suite) => Ok(Self::Suite(suite)),
335            Err(_) => match Codename::try_from(value) {
336                Ok(codename) => Ok(Self::Codename(codename)),
337                Err(_) => Err(ParseError::InvalidSuiteOrCodename),
338            },
339        }
340    }
341}
342
343impl FromStr for SuiteOrCodename {
344    type Err = ParseError;
345
346    fn from_str(s: &str) -> Result<Self, Self::Err> {
347        Self::try_from(s)
348    }
349}
350
351impl Display for SuiteOrCodename {
352    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353        match self {
354            Self::Suite(suite) => suite.fmt(f),
355            Self::Codename(codename) => codename.fmt(f),
356        }
357    }
358}
359
360impl From<SuiteOrCodename> for Suite {
361    fn from(value: SuiteOrCodename) -> Self {
362        match value {
363            SuiteOrCodename::Suite(suite) => suite,
364            SuiteOrCodename::Codename(codename) => Self::from(codename),
365        }
366    }
367}
368
369impl From<SuiteOrCodename> for Codename {
370    fn from(value: SuiteOrCodename) -> Self {
371        match value {
372            SuiteOrCodename::Suite(suite) => Self::from(suite),
373            SuiteOrCodename::Codename(codename) => codename,
374        }
375    }
376}
377
378impl Serialize for SuiteOrCodename {
379    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
380    where
381        S: Serializer,
382    {
383        serializer.serialize_str(&self.to_string())
384    }
385}
386
387impl<'de> Deserialize<'de> for SuiteOrCodename {
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390        D: serde::Deserializer<'de>,
391    {
392        deserializer.deserialize_str(TryFromStrVisitor::<Self>::new("a suite or a codename"))
393    }
394}
395
396/// Allowed values of the multi-arch field
397#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Hash)]
398#[serde(rename_all = "lowercase")]
399pub enum MultiArch {
400    /// MA: allowed
401    Allowed,
402    /// MA: foreign
403    Foreign,
404    /// MA: no
405    No,
406    /// MA: same
407    Same,
408}
409
410impl AsRef<str> for MultiArch {
411    fn as_ref(&self) -> &str {
412        match self {
413            Self::Allowed => "allowed",
414            Self::Foreign => "foreign",
415            Self::No => "no",
416            Self::Same => "same",
417        }
418    }
419}
420
421impl Display for MultiArch {
422    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
423        write!(f, "{}", self.as_ref())
424    }
425}
426
427impl TryFrom<&str> for MultiArch {
428    type Error = ParseError;
429
430    fn try_from(value: &str) -> Result<Self, Self::Error> {
431        match value {
432            "allowed" => Ok(Self::Allowed),
433            "foreign" => Ok(Self::Foreign),
434            "no" => Ok(Self::No),
435            "same" => Ok(Self::Same),
436            _ => Err(ParseError::InvalidMultiArch),
437        }
438    }
439}
440
441impl FromStr for MultiArch {
442    type Err = ParseError;
443
444    fn from_str(s: &str) -> Result<Self, Self::Err> {
445        Self::try_from(s)
446    }
447}
448
449/// Debian archive components
450#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Hash)]
451#[serde(rename_all = "lowercase")]
452pub enum Component {
453    /// The `main` archive component
454    Main,
455    /// The `contrib` archive component
456    Contrib,
457    /// The `non-free` archive component
458    #[serde(rename = "non-free")]
459    NonFree,
460    /// The `non-free-firmware` archive component
461    #[serde(rename = "non-free-firmware")]
462    NonFreeFirmware,
463}
464
465impl AsRef<str> for Component {
466    fn as_ref(&self) -> &str {
467        match self {
468            Self::Main => "main",
469            Self::Contrib => "contrib",
470            Self::NonFree => "non-free",
471            Self::NonFreeFirmware => "non-free-firmware",
472        }
473    }
474}
475
476impl Display for Component {
477    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
478        write!(f, "{}", self.as_ref())
479    }
480}
481
482impl TryFrom<&str> for Component {
483    type Error = ParseError;
484
485    fn try_from(value: &str) -> Result<Self, Self::Error> {
486        match value {
487            "main" => Ok(Self::Main),
488            "contrib" => Ok(Self::Contrib),
489            "non-free" => Ok(Self::NonFree),
490            "non-free-firmware" => Ok(Self::NonFreeFirmware),
491            _ => Err(ParseError::InvalidComponent),
492        }
493    }
494}
495
496impl FromStr for Component {
497    type Err = ParseError;
498
499    fn from_str(s: &str) -> Result<Self, Self::Err> {
500        Self::try_from(s)
501    }
502}
503
504#[cfg(test)]
505mod test {
506    use super::*;
507
508    #[test]
509    fn suite_from_str() {
510        assert_eq!(Suite::try_from("unstable").unwrap(), Suite::Unstable);
511        assert_eq!(Suite::try_from("stable").unwrap(), Suite::Stable(None));
512        assert_eq!(
513            Suite::try_from("stable-backports").unwrap(),
514            Suite::Stable(Some(Extension::Backports))
515        );
516    }
517
518    #[test]
519    fn codename_from_str() {
520        assert_eq!(Codename::try_from("sid").unwrap(), Codename::Sid);
521        assert_eq!(
522            Codename::try_from("bullseye").unwrap(),
523            Codename::Bullseye(None)
524        );
525        assert_eq!(
526            Codename::try_from("bullseye-backports").unwrap(),
527            Codename::Bullseye(Some(Extension::Backports))
528        );
529    }
530
531    #[test]
532    fn codename_from_suite() {
533        assert_eq!(Codename::from(Suite::Unstable), Codename::Sid);
534        assert_eq!(
535            Codename::from(Suite::Stable(Some(Extension::Backports))),
536            Codename::Bookworm(Some(Extension::Backports))
537        );
538    }
539
540    #[test]
541    fn suite_from_codename() {
542        assert_eq!(Suite::from(Codename::Sid), Suite::Unstable);
543        assert_eq!(
544            Suite::from(Codename::Bookworm(Some(Extension::Backports))),
545            Suite::Stable(Some(Extension::Backports))
546        );
547    }
548
549    #[test]
550    fn suite_or_codename_from_str() {
551        assert_eq!(
552            SuiteOrCodename::try_from("unstable").unwrap(),
553            SuiteOrCodename::from(Suite::Unstable)
554        );
555        assert_eq!(
556            SuiteOrCodename::try_from("sid").unwrap(),
557            SuiteOrCodename::from(Codename::Sid)
558        );
559    }
560
561    #[test]
562    fn multi_arch_from_str() {
563        assert_eq!(MultiArch::try_from("foreign").unwrap(), MultiArch::Foreign);
564    }
565
566    #[test]
567    fn compoment_from_str() {
568        assert_eq!(Component::try_from("main").unwrap(), Component::Main);
569    }
570}