alpm_types/package/file_name.rs
1//! Package filename handling.
2
3use std::{
4 fmt::Display,
5 path::{Path, PathBuf},
6 str::FromStr,
7};
8
9use alpm_parsers::traits::{AlpmParser, ParserUntil};
10use serde::{Deserialize, Serialize};
11use winnow::{
12 ModalResult,
13 Parser,
14 combinator::{opt, peek, repeat_till},
15 error::{AddContext, ContextError, ErrMode, ParserError, StrContext, StrContextValue},
16 stream::Stream,
17 token::any,
18};
19
20use crate::{
21 Architecture,
22 CompressionAlgorithmFileExtension,
23 FileTypeIdentifier,
24 FullVersion,
25 Name,
26 PackageError,
27};
28
29/// The full filename of a package.
30///
31/// A package filename tracks its [`Name`], [`FullVersion`], [`Architecture`] and the optional
32/// [`CompressionAlgorithmFileExtension`].
33#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
34#[serde(into = "String")]
35#[serde(try_from = "String")]
36pub struct PackageFileName {
37 pub(crate) name: Name,
38 pub(crate) version: FullVersion,
39 pub(crate) architecture: Architecture,
40 pub(crate) compression: Option<CompressionAlgorithmFileExtension>,
41}
42
43impl PackageFileName {
44 /// Creates a new [`PackageFileName`].
45 ///
46 /// # Errors
47 ///
48 /// Returns an error if the provided `version` does not have the `pkgrel` component.
49 ///
50 /// # Examples
51 ///
52 /// ```
53 /// use std::str::FromStr;
54 ///
55 /// use alpm_types::PackageFileName;
56 ///
57 /// # fn main() -> Result<(), alpm_types::Error> {
58 /// assert_eq!(
59 /// "example-1:1.0.0-1-x86_64.pkg.tar.zst",
60 /// PackageFileName::new(
61 /// "example".parse()?,
62 /// "1:1.0.0-1".parse()?,
63 /// "x86_64".parse()?,
64 /// Some("zst".parse()?)
65 /// )
66 /// .to_string()
67 /// );
68 /// # Ok(())
69 /// # }
70 /// ```
71 pub fn new(
72 name: Name,
73 version: FullVersion,
74 architecture: Architecture,
75 compression: Option<CompressionAlgorithmFileExtension>,
76 ) -> Self {
77 Self {
78 name,
79 version,
80 architecture,
81 compression,
82 }
83 }
84
85 /// Returns a reference to the [`Name`].
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use std::str::FromStr;
91 ///
92 /// use alpm_types::{Name, PackageFileName};
93 ///
94 /// # fn main() -> Result<(), alpm_types::Error> {
95 /// let file_name = PackageFileName::new(
96 /// "example".parse()?,
97 /// "1:1.0.0-1".parse()?,
98 /// "x86_64".parse()?,
99 /// Some("zst".parse()?),
100 /// );
101 ///
102 /// assert_eq!(file_name.name(), &Name::new("example")?);
103 /// # Ok(())
104 /// # }
105 /// ```
106 pub fn name(&self) -> &Name {
107 &self.name
108 }
109
110 /// Returns a reference to the [`FullVersion`].
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use std::str::FromStr;
116 ///
117 /// use alpm_types::{FullVersion, PackageFileName};
118 ///
119 /// # fn main() -> Result<(), alpm_types::Error> {
120 /// let file_name = PackageFileName::new(
121 /// "example".parse()?,
122 /// "1:1.0.0-1".parse()?,
123 /// "x86_64".parse()?,
124 /// Some("zst".parse()?),
125 /// );
126 ///
127 /// assert_eq!(file_name.version(), &FullVersion::from_str("1:1.0.0-1")?);
128 /// # Ok(())
129 /// # }
130 /// ```
131 pub fn version(&self) -> &FullVersion {
132 &self.version
133 }
134
135 /// Returns the [`Architecture`].
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use std::str::FromStr;
141 ///
142 /// use alpm_types::{PackageFileName, SystemArchitecture};
143 ///
144 /// # fn main() -> Result<(), alpm_types::Error> {
145 /// let file_name = PackageFileName::new(
146 /// "example".parse()?,
147 /// "1:1.0.0-1".parse()?,
148 /// "x86_64".parse()?,
149 /// Some("zst".parse()?),
150 /// );
151 ///
152 /// assert_eq!(file_name.architecture(), &SystemArchitecture::X86_64.into());
153 /// # Ok(())
154 /// # }
155 /// ```
156 pub fn architecture(&self) -> &Architecture {
157 &self.architecture
158 }
159
160 /// Returns the optional [`CompressionAlgorithmFileExtension`].
161 ///
162 /// # Examples
163 ///
164 /// ```
165 /// use std::str::FromStr;
166 ///
167 /// use alpm_types::{CompressionAlgorithmFileExtension, PackageFileName};
168 ///
169 /// # fn main() -> Result<(), alpm_types::Error> {
170 /// let file_name = PackageFileName::new(
171 /// "example".parse()?,
172 /// "1:1.0.0-1".parse()?,
173 /// "x86_64".parse()?,
174 /// Some("zst".parse()?),
175 /// );
176 ///
177 /// assert_eq!(
178 /// file_name.compression(),
179 /// Some(CompressionAlgorithmFileExtension::Zstd)
180 /// );
181 /// # Ok(())
182 /// # }
183 /// ```
184 pub fn compression(&self) -> Option<CompressionAlgorithmFileExtension> {
185 self.compression
186 }
187
188 /// Returns the [`PackageFileName`] as [`PathBuf`].
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// use std::{path::PathBuf, str::FromStr};
194 ///
195 /// use alpm_types::PackageFileName;
196 ///
197 /// # fn main() -> Result<(), alpm_types::Error> {
198 /// let file_name = PackageFileName::new(
199 /// "example".parse()?,
200 /// "1:1.0.0-1".parse()?,
201 /// "x86_64".parse()?,
202 /// Some("zst".parse()?),
203 /// );
204 ///
205 /// assert_eq!(
206 /// file_name.to_path_buf(),
207 /// PathBuf::from("example-1:1.0.0-1-x86_64.pkg.tar.zst")
208 /// );
209 /// # Ok(())
210 /// # }
211 /// ```
212 pub fn to_path_buf(&self) -> PathBuf {
213 self.to_string().into()
214 }
215
216 /// Sets the compression of the [`PackageFileName`].
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// use std::str::FromStr;
222 ///
223 /// use alpm_types::{CompressionAlgorithmFileExtension, PackageFileName};
224 ///
225 /// # fn main() -> Result<(), alpm_types::Error> {
226 /// // Create package file name with compression
227 /// let mut file_name = PackageFileName::new(
228 /// "example".parse()?,
229 /// "1:1.0.0-1".parse()?,
230 /// "x86_64".parse()?,
231 /// Some("zst".parse()?),
232 /// );
233 /// // Remove the compression
234 /// file_name.set_compression(None);
235 ///
236 /// assert!(file_name.compression().is_none());
237 ///
238 /// // Add other compression
239 /// file_name.set_compression(Some(CompressionAlgorithmFileExtension::Gzip));
240 ///
241 /// assert!(
242 /// file_name
243 /// .compression()
244 /// .is_some_and(|compression| compression == CompressionAlgorithmFileExtension::Gzip)
245 /// );
246 /// # Ok(())
247 /// # }
248 /// ```
249 pub fn set_compression(&mut self, compression: Option<CompressionAlgorithmFileExtension>) {
250 self.compression = compression
251 }
252}
253
254impl ParserUntil for PackageFileName {
255 /// Recognizes a [`PackageFileName`] in a string slice before a `delimiter`.
256 ///
257 ///
258 /// # Errors
259 ///
260 /// Returns an error if
261 ///
262 /// - the [`Name`] component can not be recognized,
263 /// - the [`FullVersion`] component can not be recognized,
264 /// - the [`Architecture`] component can not be recognized,
265 /// - or the [`CompressionAlgorithmFileExtension`] component can not be recognized.
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// use alpm_parsers::traits::ParserUntil;
271 /// use alpm_types::PackageFileName;
272 /// use winnow::Parser;
273 ///
274 /// # fn main() -> Result<(), alpm_types::Error> {
275 /// let filename = "example-package-1:1.0.0-1-x86_64.pkg.tar.zst";
276 /// assert_eq!(
277 /// filename,
278 /// PackageFileName::parser_until_eof
279 /// .parse(filename)?
280 /// .to_string()
281 /// );
282 /// # Ok(())
283 /// # }
284 /// ```
285 fn parser_until<'a, P>(delimiter: P) -> impl Parser<&'a str, Self, ErrMode<ContextError>>
286 where
287 P: Parser<&'a str, &'a str, ErrMode<ContextError>>,
288 {
289 // Define the actual parser closure.
290 // The delimiter is moved into the closure and borrowed via `by_ref()` on each call.
291 let mut delimiter_parser = delimiter;
292 move |input: &mut &'a str| -> ModalResult<Self> {
293 // Detect the amount of dashes in input and subsequently in the Name component.
294 //
295 // Note: This is a necessary step because dashes are used as delimiters between the
296 // components of the file name and the Name component (an alpm-package-name) can contain
297 // dashes, too.
298 // We know that the minimum amount of dashes in a valid alpm-package file name is
299 // three (one dash between the Name, FullVersion, PackageRelease, and Architecture
300 // component each).
301 // We rely on this fact to determine the amount of dashes in the Name component and
302 // thereby the cut-off point between the Name and the FullVersion component.
303 let checkpoint = input.checkpoint();
304 let dashes: usize =
305 repeat_till::<_, _, (), _, _, _, _>(0.., any, peek(delimiter_parser.by_ref()))
306 .take()
307 .map(|s| {
308 s.chars().fold(0, |acc, char| {
309 if char == '-' {
310 return acc + 1;
311 }
312 acc
313 })
314 })
315 .parse_next(input)?;
316 input.reset(&checkpoint);
317
318 if dashes < 3 {
319 let context_error = ContextError::from_input(input)
320 .add_context(
321 input,
322 &input.checkpoint(),
323 StrContext::Label("alpm-package file name"),
324 )
325 .add_context(
326 input,
327 &input.checkpoint(),
328 StrContext::Expected(StrContextValue::Description(
329 concat!(
330 "a package name, followed by an alpm-package-version (full or full with epoch) and an architecture.",
331 "\nAll components must be delimited with a dash ('-')."
332 )
333 ))
334 );
335
336 return Err(ErrMode::Backtrack(context_error));
337 }
338
339 // The (zero or more) dashes in the Name component.
340 let dashes_till_version = dashes.saturating_sub(2);
341
342 // Advance the parser to the dash just behind the Name component, based on the amount of
343 // dashes in the Name, e.g.:
344 // "example-package-1:1.0.0-1-x86_64.pkg.tar.zst" -> "-1:1.0.0-1-x86_64.pkg.tar.zst"
345 let name = Name::parse_name_followed_by_version(dashes_till_version)
346 .context(StrContext::Label("alpm-package-name"))
347 .parse_next(input)?;
348
349 // Consume leading dash in front of FullVersion, e.g.:
350 // "-1:1.0.0-1-x86_64.pkg.tar.zst" -> "1:1.0.0-1-x86_64.pkg.tar.zst"
351 "-".parse_next(input)?;
352
353 // Advance the parser to beyond the FullVersion component (which contains one dash),
354 // e.g.: "1:1.0.0-1-x86_64.pkg.tar.zst" -> "-x86_64.pkg.tar.zst"
355 let version: FullVersion = FullVersion::parser_until("-").parse_next(input)?;
356
357 // Consume leading dash, e.g.:
358 // "-x86_64.pkg.tar.zst" -> "x86_64.pkg.tar.zst"
359 "-".parse_next(input)?;
360
361 // Advance the parser to beyond the Architecture component, e.g.:
362 // "x86_64.pkg.tar.zst" -> ".pkg.tar.zst"
363 let architecture = Architecture::parser_until(".").parse_next(input)?;
364
365 // Consume leading dot, e.g.:
366 // ".pkg.tar.zst" -> "pkg.tar.zst"
367 ".".context(StrContext::Label("alpm-package file name"))
368 .context(StrContext::Expected(StrContextValue::StringLiteral(
369 "a `.` between the architecture and the `pkg` extension",
370 )))
371 .parse_next(input)?;
372
373 // Consume the required alpm-package file type identifier, e.g.:
374 // "pkg.tar.zst" -> ".tar.zst"
375 "pkg"
376 .context(StrContext::Label("alpm-package file type identifier"))
377 .context(StrContext::Expected(StrContextValue::StringLiteral(
378 FileTypeIdentifier::BinaryPackage.into(),
379 )))
380 .parse_next(input)?;
381
382 // Consume leading dot, e.g.:
383 // ".tar.zst" -> "tar.zst"
384 ".".context(StrContext::Label("alpm-package file name"))
385 .context(StrContext::Expected(StrContextValue::StringLiteral(
386 "a `.` between the `pkg` and `tar` extension",
387 )))
388 .parse_next(input)?;
389
390 // Consume the required tar suffix, e.g.:
391 // "tar.zst" -> ".zst"
392 "tar"
393 .context(StrContext::Label("tar suffix"))
394 .context(StrContext::Expected(StrContextValue::Description("tar")))
395 .parse_next(input)?;
396
397 // Check if there's a `.`, which hints that a CompressionAlgorithmFileExtension exists.
398 // ".zst" -> "zst"
399 // If input is "", no compression is present.
400 let has_compression = opt(".").parse_next(input)?;
401
402 let mut compression = None;
403 if has_compression.is_some() {
404 // Advance the parser for the CompressionAlgorithmFileExtension component, e.g.:
405 // "zst" -> ""
406 compression = Some(CompressionAlgorithmFileExtension::parser.parse_next(input)?);
407 }
408
409 peek(delimiter_parser.by_ref())
410 .context(StrContext::Expected(StrContextValue::Description(
411 "end of package filename",
412 )))
413 .parse_next(input)?;
414
415 Ok(Self {
416 name,
417 version,
418 architecture,
419 compression,
420 })
421 }
422 }
423}
424
425impl Display for PackageFileName {
426 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 write!(
428 f,
429 "{}-{}-{}.{}.tar{}",
430 self.name,
431 self.version,
432 self.architecture,
433 FileTypeIdentifier::BinaryPackage,
434 match self.compression {
435 None => "".to_string(),
436 Some(suffix) => format!(".{suffix}"),
437 }
438 )
439 }
440}
441
442impl From<PackageFileName> for String {
443 /// Creates a [`String`] from a [`PackageFileName`].
444 fn from(value: PackageFileName) -> Self {
445 value.to_string()
446 }
447}
448
449impl FromStr for PackageFileName {
450 type Err = crate::Error;
451
452 /// Creates a [`PackageFileName`] from a string slice.
453 ///
454 /// Delegates to [`PackageFileName::parser_until`].
455 ///
456 /// # Errors
457 ///
458 /// Returns an error if [`PackageFileName::parser_until`] fails.
459 ///
460 /// # Examples
461 ///
462 /// ```
463 /// use std::str::FromStr;
464 ///
465 /// use alpm_types::PackageFileName;
466 ///
467 /// # fn main() -> Result<(), alpm_types::Error> {
468 /// let filename = "example-package-1:1.0.0-1-x86_64.pkg.tar.zst";
469 /// assert_eq!(filename, PackageFileName::from_str(filename)?.to_string());
470 /// # Ok(())
471 /// # }
472 /// ```
473 fn from_str(s: &str) -> Result<Self, Self::Err> {
474 Ok(Self::parser_until_eof.parse(s)?)
475 }
476}
477
478impl TryFrom<&Path> for PackageFileName {
479 type Error = crate::Error;
480
481 /// Creates a [`PackageFileName`] from a [`Path`] reference.
482 ///
483 /// The file name in `value` is extracted and, if valid is turned into a string slice.
484 /// The creation of the [`PackageFileName`] is delegated to [`PackageFileName::parser_until`].
485 ///
486 /// # Errors
487 ///
488 /// Returns an error if
489 ///
490 /// - `value` does not contain a valid file name,
491 /// - `value` can not be turned into a string slice,
492 /// - or [`PackageFileName::parser_until`] fails.
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// use std::path::PathBuf;
498 ///
499 /// use alpm_types::PackageFileName;
500 ///
501 /// # fn main() -> Result<(), alpm_types::Error> {
502 /// let filename = PathBuf::from("../example-package-1:1.0.0-1-x86_64.pkg.tar.zst");
503 /// assert_eq!(
504 /// filename,
505 /// PathBuf::from("..").join(PackageFileName::try_from(filename.as_path())?.to_path_buf()),
506 /// );
507 /// # Ok(())
508 /// # }
509 /// ```
510 fn try_from(value: &Path) -> Result<Self, Self::Error> {
511 let Some(name) = value.file_name() else {
512 return Err(PackageError::InvalidPackageFileNamePath {
513 path: value.to_path_buf(),
514 }
515 .into());
516 };
517 let Some(s) = name.to_str() else {
518 return Err(PackageError::InvalidPackageFileNamePath {
519 path: value.to_path_buf(),
520 }
521 .into());
522 };
523 Ok(Self::parser_until_eof.parse(s)?)
524 }
525}
526
527impl TryFrom<String> for PackageFileName {
528 type Error = crate::Error;
529
530 /// Creates a [`PackageFileName`] from a String.
531 ///
532 /// Delegates to [`PackageFileName::parser_until`].
533 ///
534 /// # Errors
535 ///
536 /// Returns an error if [`PackageFileName::parser_until`] fails.
537 ///
538 /// # Examples
539 ///
540 /// ```
541 /// use std::str::FromStr;
542 ///
543 /// use alpm_types::PackageFileName;
544 ///
545 /// # fn main() -> Result<(), alpm_types::Error> {
546 /// let filename = "example-package-1:1.0.0-1-x86_64.pkg.tar.zst".to_string();
547 /// assert_eq!(
548 /// filename.clone(),
549 /// PackageFileName::try_from(filename)?.to_string()
550 /// );
551 /// # Ok(())
552 /// # }
553 /// ```
554 fn try_from(value: String) -> Result<Self, Self::Error> {
555 Ok(Self::parser_until_eof.parse(&value)?)
556 }
557}
558
559#[cfg(test)]
560mod test {
561 use log::{LevelFilter, debug};
562 use rstest::rstest;
563 use simplelog::{ColorChoice, Config, TermLogger, TerminalMode};
564 use testresult::TestResult;
565
566 use super::*;
567 use crate::system::SystemArchitecture;
568
569 fn init_logger() -> TestResult {
570 if TermLogger::init(
571 LevelFilter::Info,
572 Config::default(),
573 TerminalMode::Mixed,
574 ColorChoice::Auto,
575 )
576 .is_err()
577 {
578 debug!("Not initializing another logger, as one is initialized already.");
579 }
580
581 Ok(())
582 }
583
584 /// Ensures that common and uncommon cases of package filenames can be created.
585 #[rstest]
586 #[case::name_with_dashes(Name::new("example-package")?, FullVersion::from_str("1.0.0-1")?, SystemArchitecture::X86_64.into(), Some(CompressionAlgorithmFileExtension::Zstd))]
587 #[case::name_with_dashes_version_with_epoch_no_compression(Name::new("example-package")?, FullVersion::from_str("1:1.0.0-1")?, SystemArchitecture::X86_64.into(), None)]
588 fn succeed_to_create_package_file_name(
589 #[case] name: Name,
590 #[case] version: FullVersion,
591 #[case] architecture: Architecture,
592 #[case] compression: Option<CompressionAlgorithmFileExtension>,
593 ) -> TestResult {
594 init_logger()?;
595
596 let package_file_name =
597 PackageFileName::new(name.clone(), version.clone(), architecture, compression);
598 debug!("Package file name: {package_file_name}");
599
600 Ok(())
601 }
602
603 /// Tests that common and uncommon cases of package file names can be recognized and
604 /// round-tripped.
605 #[rstest]
606 #[case::name_with_dashes("example-pkg-1.0.0-1-x86_64.pkg.tar.zst")]
607 #[case::no_compression("example-pkg-1.0.0-1-x86_64.pkg.tar")]
608 #[case::version_as_name("1.0.0-1-1.0.0-1-x86_64.pkg.tar.zst")]
609 #[case::version_with_epoch("example-1:1.0.0-1-x86_64.pkg.tar.zst")]
610 #[case::version_with_pkgrel_sub_version("example-1.0.0-1.1-x86_64.pkg.tar.zst")]
611 fn succeed_to_parse_package_file_name(#[case] s: &str) -> TestResult {
612 init_logger()?;
613
614 match PackageFileName::from_str(s) {
615 Err(error) => {
616 panic!("The parser failed parsing {s} although it should have succeeded:\n{error}");
617 }
618 Ok(value) => {
619 let file_name_string: String = value.clone().into();
620 assert_eq!(file_name_string, s);
621 assert_eq!(value.to_string(), s);
622 }
623 };
624
625 Ok(())
626 }
627
628 /// Ensures that [`PackageFileName`] can be created from common and uncommon cases of package
629 /// file names as [`Path`].
630 #[rstest]
631 #[case::name_with_dashes("example-pkg-1.0.0-1-x86_64.pkg.tar.zst")]
632 #[case::no_compression("example-pkg-1.0.0-1-x86_64.pkg.tar")]
633 #[case::version_as_name("1.0.0-1-1.0.0-1-x86_64.pkg.tar.zst")]
634 #[case::version_with_epoch("example-1:1.0.0-1-x86_64.pkg.tar.zst")]
635 #[case::version_with_pkgrel_sub_version("example-1.0.0-1.1-x86_64.pkg.tar.zst")]
636 fn package_file_name_from_path_succeeds(#[case] path: &str) -> TestResult {
637 init_logger()?;
638 let path = PathBuf::from(path);
639
640 match PackageFileName::try_from(path.as_path()) {
641 Err(error) => {
642 panic!(
643 "Failed creating PackageFileName from {path:?} although it should have succeeded:\n{error}"
644 );
645 }
646 Ok(value) => assert_eq!(value.to_path_buf(), path),
647 };
648
649 Ok(())
650 }
651
652 /// Tests that a matching [`Name`] can be derived from a [`PackageFileName`].
653 #[test]
654 fn package_file_name_name() -> TestResult {
655 let name = Name::new("example")?;
656 let file_name = PackageFileName::new(
657 name.clone(),
658 "1:1.0.0-1".parse()?,
659 "x86_64".parse()?,
660 Some("zst".parse()?),
661 );
662
663 assert_eq!(file_name.name(), &name);
664
665 Ok(())
666 }
667
668 /// Tests that a matching [`FullVersion`] can be derived from a [`PackageFileName`].
669 #[test]
670 fn package_file_name_version() -> TestResult {
671 let version = FullVersion::from_str("1:1.0.0-1")?;
672 let file_name = PackageFileName::new(
673 Name::new("example")?,
674 version.clone(),
675 "x86_64".parse()?,
676 Some("zst".parse()?),
677 );
678
679 assert_eq!(file_name.version(), &version);
680
681 Ok(())
682 }
683
684 /// Tests that a matching [`Architecture`] can be derived from a [`PackageFileName`].
685 #[test]
686 fn package_file_name_architecture() -> TestResult {
687 let architecture: Architecture = SystemArchitecture::X86_64.into();
688 let file_name = PackageFileName::new(
689 Name::new("example")?,
690 "1:1.0.0-1".parse()?,
691 architecture.clone(),
692 Some("zst".parse()?),
693 );
694
695 assert_eq!(file_name.architecture(), &architecture);
696
697 Ok(())
698 }
699
700 /// Tests that a matching optional [`CompressionAlgorithmFileExtension`] can be derived from a
701 /// [`PackageFileName`].
702 #[rstest]
703 #[case::with_compression(Some(CompressionAlgorithmFileExtension::Zstd))]
704 #[case::no_compression(None)]
705 fn package_file_name_compression(
706 #[case] compression: Option<CompressionAlgorithmFileExtension>,
707 ) -> TestResult {
708 let file_name = PackageFileName::new(
709 Name::new("example")?,
710 "1:1.0.0-1".parse()?,
711 "x86_64".parse()?,
712 compression,
713 );
714
715 assert_eq!(file_name.compression(), compression);
716
717 Ok(())
718 }
719
720 /// Tests that a [`PathBuf`] can be derived from a [`PackageFileName`].
721 #[rstest]
722 #[case::with_compression(Some("zst".parse()?), "example-1:1.0.0-1-x86_64.pkg.tar.zst")]
723 #[case::no_compression(None, "example-1:1.0.0-1-x86_64.pkg.tar")]
724 fn package_file_name_to_path_buf(
725 #[case] compression: Option<CompressionAlgorithmFileExtension>,
726 #[case] path: &str,
727 ) -> TestResult {
728 let file_name = PackageFileName::new(
729 "example".parse()?,
730 "1:1.0.0-1".parse()?,
731 "x86_64".parse()?,
732 compression,
733 );
734 assert_eq!(file_name.to_path_buf(), PathBuf::from(path));
735
736 Ok(())
737 }
738
739 /// Tests that an uncompressed [`PackageFileName`] representation can be derived from a
740 /// [`PackageFileName`].
741 #[rstest]
742 #[case::compression_to_no_compression(
743 Some(CompressionAlgorithmFileExtension::Zstd),
744 None,
745 PackageFileName::new(
746 "example".parse()?,
747 "1:1.0.0-1".parse()?,
748 "x86_64".parse()?,
749 None,
750 ))]
751 #[case::no_compression_to_compression(
752 None,
753 Some(CompressionAlgorithmFileExtension::Zstd),
754 PackageFileName::new(
755 "example".parse()?,
756 "1:1.0.0-1".parse()?,
757 "x86_64".parse()?,
758 Some(CompressionAlgorithmFileExtension::Zstd),
759 ))]
760 fn package_file_name_set_compression(
761 #[case] initial_compression: Option<CompressionAlgorithmFileExtension>,
762 #[case] compression: Option<CompressionAlgorithmFileExtension>,
763 #[case] output_file_name: PackageFileName,
764 ) -> TestResult {
765 let mut file_name = PackageFileName::new(
766 "example".parse()?,
767 "1:1.0.0-1".parse()?,
768 "x86_64".parse()?,
769 initial_compression,
770 );
771 file_name.set_compression(compression);
772 assert_eq!(file_name, output_file_name);
773
774 Ok(())
775 }
776}