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}