alpm_srcinfo/schema.rs
1//! Schemas for SRCINFO data.
2
3use std::{
4 fmt::{Display, Formatter},
5 fs::File,
6 path::{Path, PathBuf},
7 str::FromStr,
8};
9
10use alpm_common::FileFormatSchema;
11use alpm_types::{SchemaVersion, semver_version::Version};
12use winnow::Parser;
13
14use crate::{Error, source_info::parser::SourceInfoContent};
15
16/// An enum tracking all available [SRCINFO] schemas.
17///
18/// The schema of a SRCINFO refers to the minimum required sections and keywords, as well as the
19/// complete set of available keywords in a specific version.
20///
21/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub enum SourceInfoSchema {
24 V1(SchemaVersion),
25}
26
27impl FileFormatSchema for SourceInfoSchema {
28 type Err = Error;
29
30 /// Returns a reference to the inner [`SchemaVersion`].
31 fn inner(&self) -> &SchemaVersion {
32 match self {
33 SourceInfoSchema::V1(v) => v,
34 }
35 }
36
37 /// Derives a [`SourceInfoSchema`] from a SRCINFO file.
38 ///
39 /// Opens the `file` and defers to [`SourceInfoSchema::derive_from_reader`].
40 ///
41 /// # Errors
42 ///
43 /// Returns an error if
44 /// - opening `file` for reading fails
45 /// - or deriving a [`SourceInfoSchema`] from the contents of `file` fails.
46 fn derive_from_file(file: impl AsRef<Path>) -> Result<Self, Error>
47 where
48 Self: Sized,
49 {
50 let file = file.as_ref();
51 Self::derive_from_reader(File::open(file).map_err(|source| {
52 Error::IoPath(
53 PathBuf::from(file),
54 "deriving schema version from SRCINFO file",
55 source,
56 )
57 })?)
58 }
59
60 /// Derives a [`SourceInfoSchema`] from SRCINFO data in a `reader`.
61 ///
62 /// Reads the `reader` to string and defers to [`SourceInfoSchema::derive_from_str`].
63 ///
64 /// # Errors
65 ///
66 /// Returns an error if
67 /// - reading a [`String`] from `reader` fails
68 /// - or deriving a [`SourceInfoSchema`] from the contents of `reader` fails.
69 fn derive_from_reader(reader: impl std::io::Read) -> Result<Self, Error>
70 where
71 Self: Sized,
72 {
73 let mut buf = String::new();
74 let mut reader = reader;
75 reader
76 .read_to_string(&mut buf)
77 .map_err(|source| Error::Io("deriving schema version from SRCINFO data", source))?;
78 Self::derive_from_str(&buf)
79 }
80
81 /// Derives a [`SourceInfoSchema`] from a string slice containing SRCINFO data.
82 ///
83 /// Since the SRCINFO format is only covered by a single version and it not carrying any
84 /// version information, this function checks whether `s` contains at least the sections
85 /// `pkgbase` and `pkgname` and the keywords `pkgver` and `pkgrel`.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use alpm_common::FileFormatSchema;
91 /// use alpm_srcinfo::SourceInfoSchema;
92 /// use alpm_types::{SchemaVersion, semver_version::Version};
93 ///
94 /// # fn main() -> Result<(), alpm_srcinfo::Error> {
95 /// let srcinfo_data = r#"
96 /// pkgbase = example
97 /// pkgdesc = An example
98 /// pkgver = 0.1.0
99 /// pkgrel = 1
100 ///
101 /// pkgname = example
102 /// "#;
103 /// # Ok(())
104 /// # }
105 /// ```
106 ///
107 /// # Errors
108 ///
109 /// Returns an error if `s` cannot be parsed.
110 fn derive_from_str(s: &str) -> Result<SourceInfoSchema, Error> {
111 let _parsed = SourceInfoContent::parser
112 .parse(s)
113 .map_err(|err| Error::ParseError(format!("{err}")))?;
114
115 Ok(SourceInfoSchema::V1(SchemaVersion::new(Version::new(
116 1, 0, 0,
117 ))))
118 }
119}
120
121impl Default for SourceInfoSchema {
122 /// Returns the default [`SourceInfoSchema`] variant ([`SourceInfoSchema::V1`]).
123 fn default() -> Self {
124 Self::V1(SchemaVersion::new(Version::new(1, 0, 0)))
125 }
126}
127
128impl FromStr for SourceInfoSchema {
129 type Err = Error;
130
131 /// Creates a [`SourceInfoSchema`] from string slice `s`.
132 ///
133 /// Relies on [`SchemaVersion::from_str`] to create a corresponding [`SourceInfoSchema`] from
134 /// `s`.
135 ///
136 /// # Errors
137 ///
138 /// Returns an error if
139 /// - no [`SchemaVersion`] can be created from `s`,
140 /// - or the conversion from [`SchemaVersion`] to [`SourceInfoSchema`] fails.
141 fn from_str(s: &str) -> Result<SourceInfoSchema, Self::Err> {
142 match SchemaVersion::from_str(s) {
143 Ok(version) => Self::try_from(version),
144 Err(_) => Err(Error::UnsupportedSchemaVersion(s.to_string())),
145 }
146 }
147}
148
149impl TryFrom<SchemaVersion> for SourceInfoSchema {
150 type Error = Error;
151
152 /// Converts a [`SchemaVersion`] to a [`SourceInfoSchema`].
153 ///
154 /// # Errors
155 ///
156 /// Returns an error if the [`SchemaVersion`]'s inner [`Version`] does not provide a major
157 /// version that corresponds to a [`SourceInfoSchema`] variant.
158 fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
159 match value.inner().major {
160 1 => Ok(SourceInfoSchema::V1(value)),
161 _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
162 }
163 }
164}
165
166impl Display for SourceInfoSchema {
167 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
168 write!(
169 fmt,
170 "{}",
171 match self {
172 SourceInfoSchema::V1(version) => version.inner().major,
173 }
174 )
175 }
176}