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