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