1use std::{
6 fmt::{Display, Formatter, Result as FmtResult, Write},
7 str::FromStr,
8};
9
10use alpm_types::{
11 Architecture,
12 BuildDate,
13 FullVersion,
14 Group,
15 InstalledSize,
16 License,
17 Name,
18 OptionalDependency,
19 PackageBaseName,
20 PackageDescription,
21 PackageInstallReason,
22 PackageRelation,
23 PackageValidation,
24 Packager,
25 RelationOrSoname,
26 Url,
27};
28use winnow::Parser;
29
30use crate::{
31 Error,
32 desc::{
33 DbDescFileV2,
34 Section,
35 parser::{SectionKeyword, sections},
36 },
37};
38
39#[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
126#[serde(deny_unknown_fields)]
127#[serde(rename_all = "lowercase")]
128pub struct DbDescFileV1 {
129 pub name: Name,
131
132 pub version: FullVersion,
134
135 pub base: PackageBaseName,
137
138 pub description: PackageDescription,
140
141 pub url: Option<Url>,
143
144 pub arch: Architecture,
146
147 pub builddate: BuildDate,
149
150 pub installdate: BuildDate,
152
153 pub packager: Packager,
155
156 pub size: InstalledSize,
158
159 pub groups: Vec<Group>,
161
162 pub reason: PackageInstallReason,
164
165 pub license: Vec<License>,
167
168 pub validation: Vec<PackageValidation>,
170
171 pub replaces: Vec<PackageRelation>,
173
174 pub depends: Vec<RelationOrSoname>,
176
177 pub optdepends: Vec<OptionalDependency>,
179
180 pub conflicts: Vec<PackageRelation>,
182
183 pub provides: Vec<RelationOrSoname>,
185}
186
187impl Display for DbDescFileV1 {
188 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
189 fn single<T: Display, W: Write>(f: &mut W, key: &str, val: &T) -> FmtResult {
191 writeln!(f, "%{key}%\n{val}\n")
192 }
193
194 fn section<T: Display, W: Write>(f: &mut W, key: &str, vals: &[T]) -> FmtResult {
196 if vals.is_empty() {
197 return Ok(());
198 }
199 writeln!(f, "%{key}%")?;
200 for v in vals {
201 writeln!(f, "{v}")?;
202 }
203 writeln!(f)
204 }
205
206 single(f, "NAME", &self.name)?;
207 single(f, "VERSION", &self.version)?;
208 single(f, "BASE", &self.base)?;
209 single(f, "DESC", &self.description)?;
210 single(
212 f,
213 "URL",
214 &self
215 .url
216 .as_ref()
217 .map_or(String::new(), |url| url.to_string()),
218 )?;
219 single(f, "ARCH", &self.arch)?;
220 single(f, "BUILDDATE", &self.builddate)?;
221 single(f, "INSTALLDATE", &self.installdate)?;
222 single(f, "PACKAGER", &self.packager)?;
223 if self.size != 0 {
225 single(f, "SIZE", &self.size)?;
226 }
227 section(f, "GROUPS", &self.groups)?;
228 if self.reason != PackageInstallReason::Explicit {
230 single(f, "REASON", &self.reason)?;
231 }
232 section(f, "LICENSE", &self.license)?;
233 section(f, "VALIDATION", &self.validation)?;
234 section(f, "REPLACES", &self.replaces)?;
235 section(f, "DEPENDS", &self.depends)?;
236 section(f, "OPTDEPENDS", &self.optdepends)?;
237 section(f, "CONFLICTS", &self.conflicts)?;
238 section(f, "PROVIDES", &self.provides)?;
239
240 Ok(())
241 }
242}
243
244impl FromStr for DbDescFileV1 {
245 type Err = Error;
246
247 fn from_str(s: &str) -> Result<Self, Self::Err> {
310 let sections = sections.parse(s)?;
311 Self::try_from(sections)
312 }
313}
314
315impl TryFrom<Vec<Section>> for DbDescFileV1 {
316 type Error = Error;
317
318 fn try_from(sections: Vec<Section>) -> Result<Self, Self::Error> {
328 let mut name = None;
329 let mut version = None;
330 let mut base = None;
331 let mut description = None;
332 let mut url = None;
333 let mut arch = None;
334 let mut builddate = None;
335 let mut installdate = None;
336 let mut packager = None;
337 let mut size = None;
338
339 let mut groups: Vec<Group> = Vec::new();
340 let mut reason = None;
341 let mut license: Vec<License> = Vec::new();
342 let mut validation = None;
343 let mut replaces: Vec<PackageRelation> = Vec::new();
344 let mut depends: Vec<RelationOrSoname> = Vec::new();
345 let mut optdepends: Vec<OptionalDependency> = Vec::new();
346 let mut conflicts: Vec<PackageRelation> = Vec::new();
347 let mut provides: Vec<RelationOrSoname> = Vec::new();
348
349 macro_rules! set_once {
351 ($field:ident, $val:expr, $kw:expr) => {{
352 if $field.is_some() {
353 return Err(Error::DuplicateSection($kw));
354 }
355 $field = Some($val);
356 }};
357 }
358
359 macro_rules! set_vec_once {
361 ($field:ident, $val:expr, $kw:expr) => {{
362 if !$field.is_empty() {
363 return Err(Error::DuplicateSection($kw));
364 }
365 $field = $val;
366 }};
367 }
368
369 for section in sections {
370 match section {
371 Section::Name(v) => set_once!(name, v, SectionKeyword::Name),
372 Section::Version(v) => set_once!(version, v, SectionKeyword::Version),
373 Section::Base(v) => set_once!(base, v, SectionKeyword::Base),
374 Section::Desc(v) => set_once!(description, v, SectionKeyword::Desc),
375 Section::Url(v) => set_once!(url, v, SectionKeyword::Url),
376 Section::Arch(v) => set_once!(arch, v, SectionKeyword::Arch),
377 Section::BuildDate(v) => set_once!(builddate, v, SectionKeyword::BuildDate),
378 Section::InstallDate(v) => set_once!(installdate, v, SectionKeyword::InstallDate),
379 Section::Packager(v) => set_once!(packager, v, SectionKeyword::Packager),
380 Section::Size(v) => set_once!(size, v, SectionKeyword::Size),
381 Section::Groups(v) => set_vec_once!(groups, v, SectionKeyword::Groups),
382 Section::Reason(v) => set_once!(reason, v, SectionKeyword::Reason),
383 Section::License(v) => set_vec_once!(license, v, SectionKeyword::License),
384 Section::Validation(v) => set_once!(validation, v, SectionKeyword::Validation),
385 Section::Replaces(v) => set_vec_once!(replaces, v, SectionKeyword::Replaces),
386 Section::Depends(v) => set_vec_once!(depends, v, SectionKeyword::Depends),
387 Section::OptDepends(v) => set_vec_once!(optdepends, v, SectionKeyword::OptDepends),
388 Section::Conflicts(v) => set_vec_once!(conflicts, v, SectionKeyword::Conflicts),
389 Section::Provides(v) => set_vec_once!(provides, v, SectionKeyword::Provides),
390 Section::XData(_) => {}
391 }
392 }
393
394 Ok(DbDescFileV1 {
395 name: name.ok_or(Error::MissingSection(SectionKeyword::Name))?,
396 version: version.ok_or(Error::MissingSection(SectionKeyword::Version))?,
397 base: base.ok_or(Error::MissingSection(SectionKeyword::Base))?,
398 description: description.ok_or(Error::MissingSection(SectionKeyword::Desc))?,
399 url: url.ok_or(Error::MissingSection(SectionKeyword::Url))?,
400 arch: arch.ok_or(Error::MissingSection(SectionKeyword::Arch))?,
401 builddate: builddate.ok_or(Error::MissingSection(SectionKeyword::BuildDate))?,
402 installdate: installdate.ok_or(Error::MissingSection(SectionKeyword::InstallDate))?,
403 packager: packager.ok_or(Error::MissingSection(SectionKeyword::Packager))?,
404 size: size.unwrap_or_default(),
405 groups,
406 reason: reason.unwrap_or(PackageInstallReason::Explicit),
407 license,
408 validation: validation
409 .filter(|v| !v.is_empty())
410 .ok_or(Error::MissingSection(SectionKeyword::Validation))?,
411 replaces,
412 depends,
413 optdepends,
414 conflicts,
415 provides,
416 })
417 }
418}
419
420impl From<DbDescFileV2> for DbDescFileV1 {
421 fn from(v2: DbDescFileV2) -> Self {
428 DbDescFileV1 {
429 name: v2.name,
430 version: v2.version,
431 base: v2.base,
432 description: v2.description,
433 url: v2.url,
434 arch: v2.arch,
435 builddate: v2.builddate,
436 installdate: v2.installdate,
437 packager: v2.packager,
438 size: v2.size,
439 groups: v2.groups,
440 reason: v2.reason,
441 license: v2.license,
442 validation: v2.validation,
443 replaces: v2.replaces,
444 depends: v2.depends,
445 optdepends: v2.optdepends,
446 conflicts: v2.conflicts,
447 provides: v2.provides,
448 }
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use rstest::*;
455 use testresult::TestResult;
456
457 use super::*;
458
459 const DESC_FULL: &str = include_str!("../../tests/correct/desc/v1/full.desc");
461
462 #[test]
463 fn depends_and_provides_accept_sonames() -> TestResult {
464 let desc = DbDescFileV1::from_str(DESC_FULL)?;
465 assert!(matches!(desc.depends[1], RelationOrSoname::SonameV1(_)));
466 assert!(matches!(desc.depends[2], RelationOrSoname::SonameV2(_)));
467 assert!(matches!(desc.provides[1], RelationOrSoname::SonameV1(_)));
468 assert!(matches!(desc.provides[2], RelationOrSoname::SonameV2(_)));
469 Ok(())
470 }
471
472 #[rstest]
473 #[case("%UNKNOWN%\nvalue", "invalid section name")]
474 #[case("%VERSION%\n1.0.0-1\n", "Missing section: %NAME%")]
475 fn invalid_desc_parser(#[case] input: &str, #[case] error_snippet: &str) {
476 let result = DbDescFileV1::from_str(input);
477 assert!(result.is_err());
478 let err = result.unwrap_err();
479 let pretty_error = err.to_string();
480 assert!(
481 pretty_error.contains(error_snippet),
482 "Error:\n=====\n{pretty_error}\n=====\nshould contain snippet:\n\n{error_snippet}"
483 );
484 }
485
486 #[test]
487 fn missing_required_section_should_fail() {
488 let input = "%VERSION%\n1.0.0-1\n";
489 let result = DbDescFileV1::from_str(input);
490 assert!(matches!(result, Err(Error::MissingSection(s)) if s == SectionKeyword::Name));
491 }
492}