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