alpm_buildinfo/
schema.rs

1use std::{
2    collections::HashMap,
3    fmt::{Display, Formatter},
4    fs::File,
5    path::{Path, PathBuf},
6    str::FromStr,
7};
8
9use alpm_common::FileFormatSchema;
10use alpm_parsers::custom_ini::parser::Item;
11use alpm_types::{SchemaVersion, semver_version::Version};
12
13use crate::Error;
14
15/// An enum describing all valid BUILDINFO schemas
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub enum BuildInfoSchema {
18    /// Schema for the [BUILDINFOv1] file format.
19    ///
20    /// [BUILDINFOv1]: https://alpm.archlinux.page/specifications/BUILDINFOv1.5.html
21    V1(SchemaVersion),
22    /// Schema for the [BUILDINFOv2] file format.
23    ///
24    /// [BUILDINFOv2]: https://alpm.archlinux.page/specifications/BUILDINFOv2.5.html
25    V2(SchemaVersion),
26}
27
28impl FileFormatSchema for BuildInfoSchema {
29    type Err = Error;
30
31    /// Returns the schema version
32    fn inner(&self) -> &SchemaVersion {
33        match self {
34            BuildInfoSchema::V1(v) => v,
35            BuildInfoSchema::V2(v) => v,
36        }
37    }
38
39    /// Derives a [`BuildInfoSchema`] from a BUILDINFO file.
40    ///
41    /// Opens the `file` and defers to [`BuildInfoSchema::derive_from_reader`].
42    ///
43    /// # Errors
44    ///
45    /// Returns an error if
46    /// - opening `file` for reading fails
47    /// - or deriving a [`BuildInfoSchema`] from the contents of `file` fails.
48    fn derive_from_file(file: impl AsRef<Path>) -> Result<Self, Error>
49    where
50        Self: Sized,
51    {
52        let file = file.as_ref();
53        Self::derive_from_reader(File::open(file).map_err(|source| {
54            Error::IoPathError(
55                PathBuf::from(file),
56                "deriving schema version from BUILDINFO file",
57                source,
58            )
59        })?)
60    }
61
62    /// Derives a [`BuildInfoSchema`] from BUILDINFO data in a `reader`.
63    ///
64    /// Reads the `reader` to string and defers to [`BuildInfoSchema::derive_from_str`].
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if
69    /// - reading a [`String`] from `reader` fails
70    /// - or deriving a [`BuildInfoSchema`] from the contents of `reader` fails.
71    fn derive_from_reader(reader: impl std::io::Read) -> Result<Self, Error>
72    where
73        Self: Sized,
74    {
75        let mut buf = String::new();
76        let mut reader = reader;
77        reader
78            .read_to_string(&mut buf)
79            .map_err(|source| Error::IoReadError {
80                context: "deriving schema version from BUILDINFO data",
81                source,
82            })?;
83        Self::derive_from_str(&buf)
84    }
85
86    /// Derives a [`BuildInfoSchema`] from a string slice containing BUILDINFO data.
87    ///
88    /// Relies on the `format` keyword and its assigned value in the BUILDINFO data to derive a
89    /// corresponding [`BuildInfoSchema`].
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use alpm_buildinfo::BuildInfoSchema;
95    /// use alpm_common::FileFormatSchema;
96    /// use alpm_types::{SchemaVersion, semver_version::Version};
97    ///
98    /// # fn main() -> Result<(), alpm_buildinfo::Error> {
99    /// let buildinfo_v2 = r#"builddate = 1
100    /// builddir = /build
101    /// startdir = /startdir
102    /// buildtool = devtools
103    /// buildtoolver = 1:1.2.1-1-any
104    /// buildenv = ccache
105    /// format = 2
106    /// installed = bar-1.2.3-1-any
107    /// options = lto
108    /// packager = Foobar McFooface <foobar@mcfooface.org>
109    /// pkgarch = any
110    /// pkgbase = foo
111    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
112    /// pkgname = foo
113    /// pkgver = 1:1.0.0-1"#;
114    ///
115    /// assert_eq!(
116    ///     BuildInfoSchema::V2(SchemaVersion::new(Version::new(2, 0, 0))),
117    ///     BuildInfoSchema::derive_from_str(buildinfo_v2)?
118    /// );
119    ///
120    /// let buildinfo_v1 = r#"builddate = 1
121    /// builddir = /build
122    /// startdir = /startdir
123    /// buildenv = ccache
124    /// format = 1
125    /// installed = bar-1.2.3-1-any
126    /// options = lto
127    /// packager = Foobar McFooface <foobar@mcfooface.org>
128    /// pkgarch = any
129    /// pkgbase = foo
130    /// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
131    /// pkgname = foo
132    /// pkgver = 1:1.0.0-1"#;
133    ///
134    /// assert_eq!(
135    ///     BuildInfoSchema::V1(SchemaVersion::new(Version::new(1, 0, 0))),
136    ///     BuildInfoSchema::derive_from_str(buildinfo_v1)?
137    /// );
138    /// # Ok(())
139    /// # }
140    /// ```
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if
145    /// - the `format` field is missing from `s`
146    /// - or deriving a [`BuildInfoSchema`] from `format` field fails.
147    fn derive_from_str(s: &str) -> Result<BuildInfoSchema, Error> {
148        // Deserialize the file into a simple map, so we can take a look at the `format` string
149        // that determines the buildinfo version.
150        let raw_buildinfo: HashMap<String, Item> = alpm_parsers::custom_ini::from_str(s)?;
151        if let Some(Item::Value(version)) = raw_buildinfo.get("format") {
152            Self::from_str(version)
153        } else {
154            Err(Error::MissingFormatField)
155        }
156    }
157}
158
159impl Default for BuildInfoSchema {
160    /// Returns the default [`BuildInfoSchema`] variant ([`BuildInfoSchema::V2`])
161    fn default() -> Self {
162        Self::V2(SchemaVersion::new(Version::new(2, 0, 0)))
163    }
164}
165
166impl FromStr for BuildInfoSchema {
167    type Err = Error;
168
169    /// Creates a [`BuildInfoSchema`] from string slice `s`.
170    ///
171    /// Relies on [`SchemaVersion::from_str`] to create a corresponding [`BuildInfoSchema`] from
172    /// `s`.
173    ///
174    /// # Errors
175    ///
176    /// Returns an error if
177    /// - no [`SchemaVersion`] can be created from `s`,
178    /// - or the conversion from [`SchemaVersion`] to [`BuildInfoSchema`] fails.
179    fn from_str(s: &str) -> Result<BuildInfoSchema, Self::Err> {
180        match SchemaVersion::from_str(s) {
181            Ok(version) => Self::try_from(version),
182            Err(_) => Err(Error::UnsupportedSchemaVersion(s.to_string())),
183        }
184    }
185}
186
187impl TryFrom<SchemaVersion> for BuildInfoSchema {
188    type Error = Error;
189
190    /// Converts a [`SchemaVersion`] to a [`BuildInfoSchema`].
191    ///
192    /// # Errors
193    ///
194    /// Returns an error if the [`SchemaVersion`]'s inner [`Version`] does not provide a major
195    /// version that corresponds to a [`BuildInfoSchema`] variant.
196    fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
197        match value.inner().major {
198            1 => Ok(BuildInfoSchema::V1(value)),
199            2 => Ok(BuildInfoSchema::V2(value)),
200            _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
201        }
202    }
203}
204
205impl Display for BuildInfoSchema {
206    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
207        write!(
208            fmt,
209            "{}",
210            match self {
211                BuildInfoSchema::V1(version) | BuildInfoSchema::V2(version) =>
212                    version.inner().major,
213            }
214        )
215    }
216}