alpm_db/desc/schema.rs
1//! Schema definition for the [alpm-db-desc] file format.
2//!
3//! [alpm-db-desc]: https://alpm.archlinux.page/specifications/alpm-db-desc.5.html
4
5use std::{
6 fmt::{Display, Formatter},
7 fs::File,
8 path::{Path, PathBuf},
9 str::FromStr,
10};
11
12use alpm_common::FileFormatSchema;
13use alpm_types::{SchemaVersion, semver_version::Version};
14
15use crate::Error;
16
17/// An enum describing all valid [alpm-db-desc] schemas.
18///
19/// Each variant corresponds to a specific revision of the
20/// specification.
21///
22/// [alpm-db-desc]: https://alpm.archlinux.page/specifications/alpm-db-desc.5.html
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub enum DbDescSchema {
25 /// Schema for the [alpm-db-descv1] file format.
26 ///
27 /// [alpm-db-descv1]: https://alpm.archlinux.page/specifications/alpm-db-descv1.5.html
28 V1(SchemaVersion),
29 /// Schema for the [alpm-db-descv2] file format.
30 ///
31 /// [alpm-db-descv2]: https://alpm.archlinux.page/specifications/alpm-db-descv2.5.html
32 V2(SchemaVersion),
33}
34
35impl FileFormatSchema for DbDescSchema {
36 type Err = Error;
37
38 /// Returns a reference to the inner [`SchemaVersion`].
39 fn inner(&self) -> &SchemaVersion {
40 match self {
41 DbDescSchema::V1(v) => v,
42 DbDescSchema::V2(v) => v,
43 }
44 }
45
46 /// Derives a [`DbDescSchema`] from an [alpm-db-desc] file on disk.
47 ///
48 /// Opens the `file` and defers to [`DbDescSchema::derive_from_reader`].
49 ///
50 /// # Errors
51 ///
52 /// Returns an error if:
53 ///
54 /// - the file cannot be opened for reading,
55 /// - or deriving a [`DbDescSchema`] from its contents fails.
56 ///
57 /// [alpm-db-desc]: https://alpm.archlinux.page/specifications/alpm-db-desc.5.html
58 fn derive_from_file(file: impl AsRef<Path>) -> Result<Self, Error>
59 where
60 Self: Sized,
61 {
62 let file = file.as_ref();
63 Self::derive_from_reader(File::open(file).map_err(|source| Error::IoPathError {
64 path: PathBuf::from(file),
65 context: "deriving schema version from DB desc file",
66 source,
67 })?)
68 }
69
70 /// Derives a [`DbDescSchema`] from [alpm-db-desc] data in a reader.
71 ///
72 /// Reads the `reader` to a string and defers to [`DbDescSchema::derive_from_str`].
73 ///
74 /// # Errors
75 ///
76 /// Returns an error if:
77 ///
78 /// - reading from `reader` fails,
79 /// - or deriving a [`DbDescSchema`] from its contents fails.
80 ///
81 /// [alpm-db-desc]: https://alpm.archlinux.page/specifications/alpm-db-desc.5.html
82 fn derive_from_reader(reader: impl std::io::Read) -> Result<Self, Error>
83 where
84 Self: Sized,
85 {
86 let mut buf = String::new();
87 let mut reader = reader;
88 reader
89 .read_to_string(&mut buf)
90 .map_err(|source| Error::IoReadError {
91 context: "deriving schema version from DB desc data",
92 source,
93 })?;
94 Self::derive_from_str(&buf)
95 }
96
97 /// Derives a [`DbDescSchema`] from a string slice containing [alpm-db-desc] data.
98 ///
99 /// The parser uses a simple heuristic:
100 ///
101 /// - v1 → no `%XDATA%` section present
102 /// - v2 → `%XDATA%` section present
103 ///
104 /// This approach avoids relying on explicit version metadata, as the DB desc
105 /// format itself is not self-describing.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// use alpm_common::FileFormatSchema;
111 /// use alpm_db::desc::DbDescSchema;
112 /// use alpm_types::{SchemaVersion, semver_version::Version};
113 ///
114 /// # fn main() -> Result<(), alpm_db::Error> {
115 /// let v1_data = r#"%NAME%
116 /// foo
117 ///
118 /// %VERSION%
119 /// 1.0.0-1
120 /// "#;
121 ///
122 /// assert_eq!(
123 /// DbDescSchema::V1(SchemaVersion::new(Version::new(1, 0, 0))),
124 /// DbDescSchema::derive_from_str(v1_data)?
125 /// );
126 ///
127 /// let v2_data = r#"%NAME%
128 /// foo
129 ///
130 /// %VERSION%
131 /// 1.0.0-1
132 ///
133 /// %XDATA%
134 /// pkgtype=pkg
135 /// "#;
136 ///
137 /// assert_eq!(
138 /// DbDescSchema::V2(SchemaVersion::new(Version::new(2, 0, 0))),
139 /// DbDescSchema::derive_from_str(v2_data)?
140 /// );
141 /// # Ok(())
142 /// # }
143 /// ```
144 ///
145 /// # Errors
146 ///
147 /// Returns an error only if internal conversion or string handling fails.
148 ///
149 /// [alpm-db-desc]: https://alpm.archlinux.page/specifications/alpm-db-desc.5.html
150 fn derive_from_str(s: &str) -> Result<DbDescSchema, Error> {
151 // Instead of an explicit "format" key, we use a heuristic:
152 // presence of `%XDATA%` implies version 2.
153 if s.contains("%XDATA%") {
154 Ok(DbDescSchema::V2(SchemaVersion::new(Version::new(2, 0, 0))))
155 } else {
156 Ok(DbDescSchema::V1(SchemaVersion::new(Version::new(1, 0, 0))))
157 }
158 }
159}
160
161impl Default for DbDescSchema {
162 /// Returns the default schema variant ([`DbDescSchema::V2`]).
163 fn default() -> Self {
164 Self::V2(SchemaVersion::new(Version::new(2, 0, 0)))
165 }
166}
167
168impl FromStr for DbDescSchema {
169 type Err = Error;
170
171 /// Parses a [`DbDescSchema`] from a version string.
172 ///
173 /// # Errors
174 ///
175 /// Returns an error if:
176 ///
177 /// - the input string is not a valid version,
178 /// - or the version does not correspond to a known schema variant.
179 fn from_str(s: &str) -> Result<DbDescSchema, 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 DbDescSchema {
188 type Error = Error;
189
190 /// Converts a [`SchemaVersion`] into a corresponding [`DbDescSchema`].
191 ///
192 /// # Errors
193 ///
194 /// Returns an error if the major version of `SchemaVersion` does not
195 /// correspond to a known [`DbDescSchema`] variant.
196 fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
197 match value.inner().major {
198 1 => Ok(DbDescSchema::V1(value)),
199 2 => Ok(DbDescSchema::V2(value)),
200 _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
201 }
202 }
203}
204
205impl Display for DbDescSchema {
206 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
207 write!(
208 fmt,
209 "{}",
210 match self {
211 DbDescSchema::V1(version) | DbDescSchema::V2(version) => version.inner().major,
212 }
213 )
214 }
215}