alpm_srcinfo/
schema.rs

1//! Schemas for SRCINFO data.
2
3use std::{
4    fmt::{Display, Formatter},
5    fs::File,
6    path::{Path, PathBuf},
7    str::FromStr,
8};
9
10use alpm_common::FileFormatSchema;
11use alpm_types::{SchemaVersion, semver_version::Version};
12use winnow::Parser;
13
14use crate::{Error, source_info::parser::SourceInfoContent};
15
16/// An enum tracking all available [SRCINFO] schemas.
17///
18/// The schema of a SRCINFO refers to the minimum required sections and keywords, as well as the
19/// complete set of available keywords in a specific version.
20///
21/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub enum SourceInfoSchema {
24    V1(SchemaVersion),
25}
26
27impl FileFormatSchema for SourceInfoSchema {
28    type Err = Error;
29
30    /// Returns a reference to the inner [`SchemaVersion`].
31    fn inner(&self) -> &SchemaVersion {
32        match self {
33            SourceInfoSchema::V1(v) => v,
34        }
35    }
36
37    /// Derives a [`SourceInfoSchema`] from a SRCINFO file.
38    ///
39    /// Opens the `file` and defers to [`SourceInfoSchema::derive_from_reader`].
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if
44    /// - opening `file` for reading fails
45    /// - or deriving a [`SourceInfoSchema`] from the contents of `file` fails.
46    fn derive_from_file(file: impl AsRef<Path>) -> Result<Self, Error>
47    where
48        Self: Sized,
49    {
50        let file = file.as_ref();
51        Self::derive_from_reader(File::open(file).map_err(|source| {
52            Error::IoPath(
53                PathBuf::from(file),
54                "deriving schema version from SRCINFO file",
55                source,
56            )
57        })?)
58    }
59
60    /// Derives a [`SourceInfoSchema`] from SRCINFO data in a `reader`.
61    ///
62    /// Reads the `reader` to string and defers to [`SourceInfoSchema::derive_from_str`].
63    ///
64    /// # Errors
65    ///
66    /// Returns an error if
67    /// - reading a [`String`] from `reader` fails
68    /// - or deriving a [`SourceInfoSchema`] from the contents of `reader` fails.
69    fn derive_from_reader(reader: impl std::io::Read) -> Result<Self, Error>
70    where
71        Self: Sized,
72    {
73        let mut buf = String::new();
74        let mut reader = reader;
75        reader
76            .read_to_string(&mut buf)
77            .map_err(|source| Error::Io("deriving schema version from SRCINFO data", source))?;
78        Self::derive_from_str(&buf)
79    }
80
81    /// Derives a [`SourceInfoSchema`] from a string slice containing SRCINFO data.
82    ///
83    /// Since the SRCINFO format is only covered by a single version and it not carrying any
84    /// version information, this function checks whether `s` contains at least the sections
85    /// `pkgbase` and `pkgname` and the keywords `pkgver` and `pkgrel`.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use alpm_common::FileFormatSchema;
91    /// use alpm_srcinfo::SourceInfoSchema;
92    /// use alpm_types::{SchemaVersion, semver_version::Version};
93    ///
94    /// # fn main() -> Result<(), alpm_srcinfo::Error> {
95    /// let srcinfo_data = r#"
96    /// pkgbase = example
97    ///     pkgdesc = An example
98    ///     pkgver = 0.1.0
99    ///     pkgrel = 1
100    ///
101    /// pkgname = example
102    /// "#;
103    /// # Ok(())
104    /// # }
105    /// ```
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if `s` cannot be parsed.
110    fn derive_from_str(s: &str) -> Result<SourceInfoSchema, Error> {
111        let _parsed = SourceInfoContent::parser
112            .parse(s)
113            .map_err(|err| Error::ParseError(format!("{err}")))?;
114
115        Ok(SourceInfoSchema::V1(SchemaVersion::new(Version::new(
116            1, 0, 0,
117        ))))
118    }
119}
120
121impl Default for SourceInfoSchema {
122    /// Returns the default [`SourceInfoSchema`] variant ([`SourceInfoSchema::V1`]).
123    fn default() -> Self {
124        Self::V1(SchemaVersion::new(Version::new(1, 0, 0)))
125    }
126}
127
128impl FromStr for SourceInfoSchema {
129    type Err = Error;
130
131    /// Creates a [`SourceInfoSchema`] from string slice `s`.
132    ///
133    /// Relies on [`SchemaVersion::from_str`] to create a corresponding [`SourceInfoSchema`] from
134    /// `s`.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error if
139    /// - no [`SchemaVersion`] can be created from `s`,
140    /// - or the conversion from [`SchemaVersion`] to [`SourceInfoSchema`] fails.
141    fn from_str(s: &str) -> Result<SourceInfoSchema, Self::Err> {
142        match SchemaVersion::from_str(s) {
143            Ok(version) => Self::try_from(version),
144            Err(_) => Err(Error::UnsupportedSchemaVersion(s.to_string())),
145        }
146    }
147}
148
149impl TryFrom<SchemaVersion> for SourceInfoSchema {
150    type Error = Error;
151
152    /// Converts a [`SchemaVersion`] to a [`SourceInfoSchema`].
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if the [`SchemaVersion`]'s inner [`Version`] does not provide a major
157    /// version that corresponds to a [`SourceInfoSchema`] variant.
158    fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
159        match value.inner().major {
160            1 => Ok(SourceInfoSchema::V1(value)),
161            _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
162        }
163    }
164}
165
166impl Display for SourceInfoSchema {
167    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
168        write!(
169            fmt,
170            "{}",
171            match self {
172                SourceInfoSchema::V1(version) => version.inner().major,
173            }
174        )
175    }
176}