alpm_buildinfo/build_info/
v1.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use alpm_types::{
7    Architecture,
8    BuildDate,
9    BuildDirectory,
10    BuildEnvironmentOption,
11    Checksum,
12    FullVersion,
13    InstalledPackage,
14    Name,
15    PackageOption,
16    Packager,
17    SchemaVersion,
18    digests::Sha256,
19    semver_version::Version as SemverVersion,
20};
21use serde_with::{DisplayFromStr, serde_as};
22
23use crate::{BuildInfoSchema, Error, build_info::format::BuildInfoFormat};
24
25/// BUILDINFO version 1
26///
27/// `BuildInfoV1` is (exclusively) compatible with data following the first specification of the
28/// BUILDINFO file.
29///
30/// ## Examples
31///
32/// ```
33/// use std::str::FromStr;
34///
35/// use alpm_buildinfo::BuildInfoV1;
36///
37/// # fn main() -> Result<(), alpm_buildinfo::Error> {
38/// let buildinfo_data = r#"format = 1
39/// pkgname = foo
40/// pkgbase = foo
41/// pkgver = 1:1.0.0-1
42/// pkgarch = any
43/// pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
44/// packager = Foobar McFooface <foobar@mcfooface.org>
45/// builddate = 1
46/// builddir = /build
47/// buildenv = ccache
48/// buildenv = color
49/// options = lto
50/// options = !strip
51/// installed = bar-1.2.3-1-any
52/// installed = beh-2.2.3-4-any
53/// "#;
54///
55/// let buildinfo = BuildInfoV1::from_str(buildinfo_data)?;
56/// assert_eq!(buildinfo.to_string(), buildinfo_data);
57/// # Ok(())
58/// # }
59/// ```
60#[serde_as]
61#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde_more::SerializeMore)]
62#[more(key = "format", position = "front")]
63pub struct BuildInfoV1 {
64    /// The package name
65    #[serde_as(as = "DisplayFromStr")]
66    pub pkgname: Name,
67
68    /// The package base name
69    #[serde_as(as = "DisplayFromStr")]
70    pub pkgbase: Name,
71
72    /// The package version
73    #[serde_as(as = "DisplayFromStr")]
74    pub pkgver: FullVersion,
75
76    /// The package architecture
77    #[serde_as(as = "DisplayFromStr")]
78    pub pkgarch: Architecture,
79
80    /// The package build SHA-256 checksum
81    #[serde_as(as = "DisplayFromStr")]
82    pub pkgbuild_sha256sum: Checksum<Sha256>,
83
84    /// The packager
85    #[serde_as(as = "DisplayFromStr")]
86    pub packager: Packager,
87
88    /// The build date
89    #[serde_as(as = "DisplayFromStr")]
90    pub builddate: BuildDate,
91
92    /// The build directory
93    #[serde_as(as = "DisplayFromStr")]
94    pub builddir: BuildDirectory,
95
96    /// The build environment
97    #[serde_as(as = "Vec<DisplayFromStr>")]
98    #[serde(default)]
99    pub buildenv: Vec<BuildEnvironmentOption>,
100
101    /// The package options
102    #[serde_as(as = "Vec<DisplayFromStr>")]
103    #[serde(default)]
104    pub options: Vec<PackageOption>,
105
106    /// The installed packages
107    #[serde_as(as = "Vec<DisplayFromStr>")]
108    #[serde(default)]
109    pub installed: Vec<InstalledPackage>,
110}
111
112impl BuildInfoV1 {
113    /// Used by serde_more to serialize the additional `format` field.
114    fn format(&self) -> String {
115        BuildInfoSchema::V1(SchemaVersion::new(SemverVersion::new(1, 0, 0))).to_string()
116    }
117}
118
119impl FromStr for BuildInfoV1 {
120    type Err = Error;
121    /// Create a BuildInfoV1 from a &str
122    ///
123    /// # Errors
124    ///
125    /// Returns an `Error` if any of the fields in `input` can not be validated according to
126    /// `BuildInfoV1` or their respective own specification.
127    fn from_str(input: &str) -> Result<BuildInfoV1, Self::Err> {
128        let build_info_format: BuildInfoFormat = alpm_parsers::custom_ini::from_str(input)?;
129        let schema_version: SchemaVersion = build_info_format.into();
130        if schema_version.inner().major != 1 {
131            return Err(Error::WrongSchemaVersion(schema_version));
132        }
133
134        let buildinfo: BuildInfoV1 = alpm_parsers::custom_ini::from_str(input)?;
135        Ok(buildinfo)
136    }
137}
138
139impl Display for BuildInfoV1 {
140    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
141        write!(
142            fmt,
143            "format = {}\n\
144            pkgname = {}\n\
145            pkgbase = {}\n\
146            pkgver = {}\n\
147            pkgarch = {}\n\
148            pkgbuild_sha256sum = {}\n\
149            packager = {}\n\
150            builddate = {}\n\
151            builddir = {}\n\
152            {}\n\
153            {}\n\
154            {}\n\
155            ",
156            self.format(),
157            self.pkgname,
158            self.pkgbase,
159            self.pkgver,
160            self.pkgarch,
161            self.pkgbuild_sha256sum,
162            self.packager,
163            self.builddate,
164            self.builddir,
165            self.buildenv
166                .iter()
167                .map(|v| format!("buildenv = {v}"))
168                .collect::<Vec<String>>()
169                .join("\n"),
170            self.options
171                .iter()
172                .map(|v| format!("options = {v}"))
173                .collect::<Vec<String>>()
174                .join("\n"),
175            self.installed
176                .iter()
177                .map(|v| format!("installed = {v}"))
178                .collect::<Vec<String>>()
179                .join("\n"),
180        )
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use rstest::{fixture, rstest};
187    use testresult::TestResult;
188
189    use super::*;
190
191    #[fixture]
192    fn valid_buildinfov1() -> String {
193        r#"format = 1
194builddate = 1
195builddir = /build
196buildenv = ccache
197buildenv = color
198installed = bar-1.2.3-1-any
199installed = beh-2.2.3-4-any
200options = lto
201options = !strip
202packager = Foobar McFooface <foobar@mcfooface.org>
203pkgarch = any
204pkgbase = foo
205pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
206pkgname = foo
207pkgver = 1:1.0.0-1
208"#
209        .to_string()
210    }
211
212    #[fixture]
213    fn invalid_format_buildinfov1() -> String {
214        r#"format = 2
215builddate = 1
216builddir = /build
217buildenv = ccache
218buildenv = color
219installed = bar-1.2.3-1-any
220installed = beh-2.2.3-4-any
221options = lto
222options = !strip
223packager = Foobar McFooface <foobar@mcfooface.org>
224pkgarch = any
225pkgbase = foo
226pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
227pkgname = foo
228pkgver = 1:1.0.0-1
229"#
230        .to_string()
231    }
232
233    #[rstest]
234    fn buildinfov1_from_str(valid_buildinfov1: String) -> TestResult {
235        BuildInfoV1::from_str(&valid_buildinfov1)?;
236        Ok(())
237    }
238
239    #[rstest]
240    fn buildinfov1() -> TestResult {
241        BuildInfoV1 {
242            builddate: 1,
243            builddir: BuildDirectory::from_str("/build")?,
244            buildenv: vec![BuildEnvironmentOption::new("check")?],
245            installed: vec![InstalledPackage::from_str("bar-1:1.0.0-2-any")?],
246            options: vec![PackageOption::new("lto")?],
247            packager: Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?,
248            pkgarch: Architecture::Any,
249            pkgbase: Name::new("foo")?,
250            pkgbuild_sha256sum: Checksum::<Sha256>::calculate_from("foo"),
251            pkgname: Name::new("foo")?,
252            pkgver: FullVersion::from_str("1:1.0.0-1")?,
253        };
254        Ok(())
255    }
256
257    #[rstest]
258    fn buildinfov1_invalid_schemaversion(invalid_format_buildinfov1: String) -> TestResult {
259        assert!(BuildInfoV1::from_str(&invalid_format_buildinfov1).is_err());
260        Ok(())
261    }
262
263    #[rstest]
264    #[case("builddate = 2")]
265    #[case("builddir = /build2")]
266    #[case("format = 1")]
267    #[case("packager = Foobar McFooface <foobar@mcfooface.org>")]
268    #[case("pkgarch = any")]
269    #[case("pkgbase = foo")]
270    #[case("pkgbuild_sha256sum = b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c")]
271    #[case("pkgname = foo")]
272    #[case("pkgver = 1:1.0.0-1")]
273    fn buildinfov1_from_str_duplicate_fail(mut valid_buildinfov1: String, #[case] duplicate: &str) {
274        valid_buildinfov1.push_str(duplicate);
275        assert!(BuildInfoV1::from_str(&valid_buildinfov1).is_err());
276    }
277}