Skip to main content

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