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