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}