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}