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}