Skip to main content

alpm_types/
system.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use alpm_parsers::prelude::*;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9#[cfg(feature = "serde")]
10use serde_with::DeserializeFromStr;
11use strum::{Display, EnumString, VariantNames};
12use winnow::{
13    ascii::Caseless,
14    combinator::{alt, cut_err, eof, not, repeat},
15    error::ErrMode,
16    token::{one_of, take_while},
17};
18
19use crate::Error;
20
21/// Specific CPU architecture
22///
23/// Can be either a known variant or an unknown architecture represented as
24/// a case-insensitive string, that:
25///
26/// - consists only of ASCII alphanumeric characters and underscores
27/// - is not "any"
28///
29/// Members of the [`SystemArchitecture`] enum can be created from `&str`.
30///
31/// ## Examples
32/// ```
33/// use std::str::FromStr;
34///
35/// use alpm_types::{SystemArchitecture, UnknownArchitecture};
36///
37/// # fn main() -> Result<(), alpm_types::Error> {
38/// // create SystemArchitecture from str
39/// assert_eq!(
40///     SystemArchitecture::from_str("aarch64")?,
41///     SystemArchitecture::Aarch64
42/// );
43///
44/// // Format as String
45/// assert_eq!("x86_64", format!("{}", SystemArchitecture::X86_64));
46/// assert_eq!(
47///     "custom_arch",
48///     format!("{}", SystemArchitecture::from_str("custom_arch")?)
49/// );
50/// # Ok(())
51/// # }
52/// ```
53#[derive(Clone, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd, VariantNames)]
54#[cfg_attr(feature = "serde", derive(DeserializeFromStr, Serialize))]
55#[strum(serialize_all = "lowercase")]
56#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
57pub enum SystemArchitecture {
58    /// ARMv8 64-bit
59    Aarch64,
60    /// ARM
61    Arm,
62    /// ARMv6 hard-float
63    Armv6h,
64    /// ARMv7 hard-float
65    Armv7h,
66    /// Intel 386
67    I386,
68    /// Intel 486
69    I486,
70    /// Intel 686
71    I686,
72    /// LoongArch 64-bit
73    Loong64,
74    /// Intel Pentium 4
75    Pentium4,
76    /// RISC-V 32-bit
77    Riscv32,
78    /// RISC-V 64-bit
79    Riscv64,
80    /// Intel x86_64
81    X86_64,
82    /// Intel x86_64 version 2
83    #[strum(to_string = "x86_64_v2")]
84    #[cfg_attr(feature = "serde", serde(rename = "x86_64_v2"))]
85    X86_64V2,
86    /// Intel x86_64 version 3
87    #[strum(to_string = "x86_64_v3")]
88    #[cfg_attr(feature = "serde", serde(rename = "x86_64_v3"))]
89    X86_64V3,
90    /// Intel x86_64 version 4
91    #[strum(to_string = "x86_64_v4")]
92    #[cfg_attr(feature = "serde", serde(rename = "x86_64_v4"))]
93    X86_64V4,
94    /// Unknown architecture
95    #[strum(transparent)]
96    #[cfg_attr(feature = "serde", serde(untagged))]
97    Unknown(UnknownArchitecture),
98}
99
100impl AlpmParser for SystemArchitecture {
101    /// Recognizes a [`SystemArchitecture`] in an input string.
102    ///
103    /// # Errors
104    ///
105    /// Returns an error if `input` does not begin with a valid `SystemArchitecture`.
106    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
107        let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
108            // Make sure we don't have an `any`.
109            cut_err(not((Caseless("any"), eof)))
110                .label("system architecture. 'any' has a special meaning and is not allowed here.")
111                .parse_next(input)?;
112
113            let alphanum = |c: char| c.is_ascii_alphanumeric();
114            let special_chars = ['_'];
115
116            // We consume as many valid characters as we can until we hit an unknown char or `eof`.
117            // E.g.
118            // `asdfasdf_x86_64_omega:test` -> `:test`
119            let architecture: String = cut_err(repeat(1.., one_of((alphanum, special_chars))))
120                .label("character in system architecture")
121                .expected_text(
122                    "a string containing only ASCII alphanumeric characters and underscores.",
123                )
124                .parse_next(input)?;
125
126            // We now take that valid architecture and check it against all known static variants in
127            // our SystemArchitecture enum.
128            // If none of those match, return it as an SystemArchitecture::Unknown.
129            let architecture = match architecture.as_str() {
130                // Handle all static variants
131                "aarch64" => SystemArchitecture::Aarch64,
132                "arm" => SystemArchitecture::Arm,
133                "armv6h" => SystemArchitecture::Armv6h,
134                "armv7h" => SystemArchitecture::Armv7h,
135                "i386" => SystemArchitecture::I386,
136                "i486" => SystemArchitecture::I486,
137                "i686" => SystemArchitecture::I686,
138                "loong64" => SystemArchitecture::Loong64,
139                "pentium4" => SystemArchitecture::Pentium4,
140                "riscv32" => SystemArchitecture::Riscv32,
141                "riscv64" => SystemArchitecture::Riscv64,
142                "x86_64" => SystemArchitecture::X86_64,
143                "x86_64_v2" => SystemArchitecture::X86_64V2,
144                "x86_64_v3" => SystemArchitecture::X86_64V3,
145                "x86_64_v4" => SystemArchitecture::X86_64V4,
146                // Generic fallback handler.
147                other => SystemArchitecture::Unknown(UnknownArchitecture(other.to_string())),
148            };
149
150            Ok(architecture)
151        };
152
153        parser.layer("system architecture").parse_next(input)
154    }
155
156    fn delimiter_error_context<'a, O, P>(
157        parser: P,
158    ) -> impl Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>
159    where
160        P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>,
161    {
162        parser
163            .label("character in system architecture")
164            .expected_text(
165                "a string containing only ASCII alphanumeric characters and underscores.",
166            )
167            .layer("system architecture")
168    }
169}
170
171impl FromStr for SystemArchitecture {
172    type Err = Error;
173
174    /// Creates a [`SystemArchitecture`] from a string slice.
175    ///
176    /// Delegates to [`SystemArchitecture::parser`].
177    ///
178    /// # Errors
179    ///
180    /// Returns an error if [`SystemArchitecture::parser`] fails.
181    fn from_str(s: &str) -> Result<SystemArchitecture, Self::Err> {
182        Ok(Self::parser_until_eof.parse(Input::new(s))?)
183    }
184}
185
186/// An unknown architecture that is a valid [alpm-architecture].
187///
188/// # Note
189///
190/// This type can only be created via [`SystemArchitecture`].
191///
192/// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
193#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
194#[cfg_attr(feature = "serde", derive(Serialize))]
195pub struct UnknownArchitecture(String);
196
197#[cfg(feature = "serde")]
198impl<'de> Deserialize<'de> for UnknownArchitecture {
199    /// Deserializes an [`UnknownArchitecture`] from a string.
200    ///
201    /// This uses [`SystemArchitecture::from_str`] for validation, as [`UnknownArchitecture`]
202    /// can only be created via [`SystemArchitecture`].
203    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204    where
205        D: serde::Deserializer<'de>,
206    {
207        let s = String::deserialize(deserializer)?;
208        match SystemArchitecture::from_str(&s).map_err(serde::de::Error::custom)? {
209            SystemArchitecture::Unknown(architecture) => Ok(architecture),
210            architecture => Err(serde::de::Error::custom(format!(
211                "expected an unknown architecture, but {architecture} is a known architecture"
212            ))),
213        }
214    }
215}
216
217impl From<UnknownArchitecture> for SystemArchitecture {
218    /// Converts an [`UnknownArchitecture`] into a [`SystemArchitecture`].
219    fn from(value: UnknownArchitecture) -> Self {
220        SystemArchitecture::Unknown(value)
221    }
222}
223
224impl From<UnknownArchitecture> for Architecture {
225    /// Converts an [`UnknownArchitecture`] into an [`Architecture`].
226    fn from(value: UnknownArchitecture) -> Self {
227        Architecture::Some(value.into())
228    }
229}
230
231impl Display for UnknownArchitecture {
232    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
233        write!(fmt, "{}", self.as_ref())
234    }
235}
236
237impl AsRef<str> for UnknownArchitecture {
238    fn as_ref(&self) -> &str {
239        &self.0
240    }
241}
242
243/// A valid [alpm-architecture], either "any" or a specific [`SystemArchitecture`].
244///
245/// Members of the [`Architecture`] enum can be created from `&str`.
246///
247/// ## Examples
248///
249/// ```
250/// use std::str::FromStr;
251///
252/// use alpm_types::{Architecture, SystemArchitecture, UnknownArchitecture};
253///
254/// # fn main() -> Result<(), alpm_types::Error> {
255/// // create Architecture from str
256/// assert_eq!(
257///     Architecture::from_str("aarch64")?,
258///     Architecture::Some(SystemArchitecture::Aarch64)
259/// );
260/// assert_eq!(Architecture::from_str("any")?, Architecture::Any);
261///
262/// // format as String
263/// assert_eq!("any", format!("{}", Architecture::Any));
264/// assert_eq!(
265///     "x86_64",
266///     format!("{}", Architecture::Some(SystemArchitecture::X86_64))
267/// );
268/// assert_eq!(
269///     "custom_arch",
270///     format!("{}", Architecture::from_str("custom_arch")?)
271/// );
272/// # Ok(())
273/// # }
274/// ```
275///
276/// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
277#[derive(Clone, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd, VariantNames)]
278#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
279#[strum(serialize_all = "lowercase")]
280#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
281pub enum Architecture {
282    /// Any architecture
283    Any,
284    /// Specific architecture
285    #[strum(transparent)]
286    #[cfg_attr(feature = "serde", serde(untagged))]
287    Some(SystemArchitecture),
288}
289
290impl AlpmParser for Architecture {
291    /// Recognizes an [`Architecture`] in an input string.
292    ///
293    /// # Errors
294    ///
295    /// Returns an error if `input` does not contain a valid [`Architecture`].
296    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Architecture> {
297        alt((
298            Caseless("any").value(Architecture::Any),
299            SystemArchitecture::parser.map(Architecture::Some),
300        ))
301        .label("alpm-architecture")
302        .layer("alpm-architecture")
303        .parse_next(input)
304    }
305
306    fn delimiter_error_context<'a, O, P>(
307        parser: P,
308    ) -> impl Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>
309    where
310        P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>,
311    {
312        parser
313            .label("character in architecture")
314            .expected_text(
315                "a string containing only ASCII alphanumeric characters and underscores.",
316            )
317            .layer("alpm-architecture")
318    }
319}
320
321impl FromStr for Architecture {
322    type Err = Error;
323
324    /// Creates an [`Architecture`] from a string slice.
325    ///
326    /// Delegates to [`Architecture::parser`].
327    ///
328    /// # Errors
329    ///
330    /// Returns an error if [`Architecture::parser`] fails.
331    fn from_str(s: &str) -> Result<Architecture, Self::Err> {
332        Ok(Self::parser_until_eof.parse(Input::new(s))?)
333    }
334}
335
336impl From<SystemArchitecture> for Architecture {
337    /// Converts a [`SystemArchitecture`] into an [`Architecture`].
338    fn from(value: SystemArchitecture) -> Self {
339        Architecture::Some(value)
340    }
341}
342
343/// Represents multiple valid [alpm-architecture]s.
344///
345/// Can be either "any" or multiple specific [`SystemArchitecture`]s.
346///
347/// [`Architectures`] enum can be created from a vector of [`Architecture`]s using a [`TryFrom`]
348/// implementation.
349///
350/// [alpm-architecture]: https://alpm.archlinux.page/specifications/alpm-architecture.7.html
351#[derive(Clone, Debug, EnumString, Eq, Hash, Ord, PartialEq, PartialOrd, VariantNames)]
352#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
353#[strum(serialize_all = "lowercase")]
354#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
355pub enum Architectures {
356    /// Any architecture
357    Any,
358    /// Specific architectures
359    #[strum(transparent)]
360    #[cfg_attr(feature = "serde", serde(untagged))]
361    Some(Vec<SystemArchitecture>),
362}
363
364impl Architectures {
365    /// Returns the number of entries in the architectures list.
366    pub fn len(&self) -> usize {
367        match self {
368            Architectures::Any => 1,
369            Architectures::Some(archs) => archs.len(),
370        }
371    }
372
373    /// Returns `true` if there are no entries in the architectures list.
374    pub fn is_empty(&self) -> bool {
375        match self {
376            Architectures::Any => false,
377            Architectures::Some(archs) => archs.is_empty(),
378        }
379    }
380}
381
382impl Display for Architectures {
383    /// Formats the [`Architectures`] as a comma-separated string.
384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385        match self {
386            Architectures::Any => {
387                write!(f, "any")
388            }
389            Architectures::Some(archs) => {
390                write!(
391                    f,
392                    "{}",
393                    archs
394                        .iter()
395                        .map(ToString::to_string)
396                        .collect::<Vec<_>>()
397                        .join(", ")
398                )
399            }
400        }
401    }
402}
403
404impl From<Architecture> for Architectures {
405    /// Converts a single [`Architecture`] into an [`Architectures`].
406    fn from(value: Architecture) -> Self {
407        match value {
408            Architecture::Any => Architectures::Any,
409            Architecture::Some(arch) => Architectures::Some(vec![arch]),
410        }
411    }
412}
413
414impl From<&Architectures> for Vec<Architecture> {
415    /// Converts an [`Architectures`] into a vector of [`Architecture`]s.
416    fn from(value: &Architectures) -> Self {
417        match value {
418            Architectures::Any => vec![Architecture::Any],
419            Architectures::Some(archs) => {
420                archs.clone().into_iter().map(Architecture::Some).collect()
421            }
422        }
423    }
424}
425
426impl IntoIterator for &Architectures {
427    type Item = Architecture;
428    type IntoIter = std::vec::IntoIter<Architecture>;
429    /// Creates an iterator over [`Architecture`]s.
430    fn into_iter(self) -> Self::IntoIter {
431        let vec: Vec<Architecture> = self.into();
432        vec.into_iter()
433    }
434}
435
436impl TryFrom<Vec<&Architecture>> for Architectures {
437    type Error = Error;
438
439    /// Tries to convert a vector of [`Architecture`] into an [`Architectures`].
440    ///
441    /// # Errors
442    ///
443    /// The conversion fails if the input vector contains [`Architecture::Any`] along with other
444    /// architectures.
445    fn try_from(value: Vec<&Architecture>) -> Result<Self, Self::Error> {
446        if value.contains(&&Architecture::Any) {
447            if value.len() > 1 {
448                Err(Error::InvalidArchitectures {
449                    architectures: value.iter().map(|&v| v.clone()).collect(),
450                    context: "'any' cannot be used in combination with other architectures.",
451                })
452            } else {
453                Ok(Architectures::Any)
454            }
455        } else {
456            let archs: Vec<SystemArchitecture> = value
457                .into_iter()
458                .map(|arch| {
459                    if let Architecture::Some(specific) = arch {
460                        specific.clone()
461                    } else {
462                        // This case is already handled above
463                        unreachable!()
464                    }
465                })
466                .collect();
467            Ok(Architectures::Some(archs))
468        }
469    }
470}
471
472impl TryFrom<Vec<Architecture>> for Architectures {
473    type Error = Error;
474
475    /// Tries to convert a vector of [`Architecture`] into an [`Architectures`].
476    ///
477    /// Delegates to the [`TryFrom`] implementation for `Vec<&Architecture>`.
478    fn try_from(value: Vec<Architecture>) -> Result<Self, Self::Error> {
479        value.iter().collect::<Vec<&Architecture>>().try_into()
480    }
481}
482
483/// ELF architecture format.
484///
485/// This enum represents the _Class_ field in the [_ELF Header_].
486///
487/// ## Examples
488///
489/// ```
490/// use std::str::FromStr;
491///
492/// use alpm_types::ElfArchitectureFormat;
493///
494/// # fn main() -> Result<(), alpm_types::Error> {
495/// // create ElfArchitectureFormat from str
496/// assert_eq!(
497///     ElfArchitectureFormat::from_str("32"),
498///     Ok(ElfArchitectureFormat::Bit32)
499/// );
500/// assert_eq!(
501///     ElfArchitectureFormat::from_str("64"),
502///     Ok(ElfArchitectureFormat::Bit64)
503/// );
504///
505/// // format as String
506/// assert_eq!("32", format!("{}", ElfArchitectureFormat::Bit32));
507/// assert_eq!("64", format!("{}", ElfArchitectureFormat::Bit64));
508/// # Ok(())
509/// # }
510/// ```
511///
512/// [_ELF Header_]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#ELF_header
513#[derive(Clone, Copy, Debug, Display, EnumString, Eq, Ord, PartialEq, PartialOrd)]
514#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
515#[strum(serialize_all = "lowercase")]
516pub enum ElfArchitectureFormat {
517    /// 32-bit
518    #[strum(to_string = "32")]
519    Bit32 = 32,
520    /// 64-bit
521    #[strum(to_string = "64")]
522    Bit64 = 64,
523}
524
525impl AlpmParser for ElfArchitectureFormat {
526    /// Recognizes an [`ElfArchitectureFormat`] in a string slice.
527    ///
528    /// # Errors
529    ///
530    /// Returns an error, if `input` does not begin with a valid [`ElfArchitectureFormat`].
531    fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
532        take_while(1.., |c: char| c.is_ascii_digit())
533            .try_map(ElfArchitectureFormat::from_str)
534            .expected_string("32 or 64")
535            .layer("ELF architecture")
536            .parse_next(input)
537    }
538
539    fn delimiter_error_context<'a, O, P>(
540        parser: P,
541    ) -> impl Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>
542    where
543        P: Parser<Input<'a>, O, ErrMode<ParseStack<'a>>>,
544    {
545        parser.expected_string("32 or 64").layer("ELF architecture")
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use std::str::FromStr;
552
553    use insta::assert_snapshot;
554    use rstest::rstest;
555    use strum::ParseError;
556    use testresult::TestResult;
557
558    use super::*;
559    use crate::configure_insta;
560
561    /// Ensures, that known CPU architectures are parsed as [`SystemArchitecture`] and unknown as
562    /// [`UnknownArchitecture`].
563    #[rstest]
564    #[case("aarch64", SystemArchitecture::Aarch64)]
565    #[case("f_oo", UnknownArchitecture("f_oo".to_string()).into())]
566    fn system_architecture_from_string(
567        #[case] s: &str,
568        #[case] arch: SystemArchitecture,
569    ) -> TestResult {
570        assert_eq!(SystemArchitecture::from_str(s)?, arch);
571
572        Ok(())
573    }
574
575    #[rstest]
576    #[case("f oo")]
577    #[case("any")]
578    fn invalid_system_architecture_from_string(#[case] input: &str) {
579        let (test_name, _guard) = configure_insta();
580
581        let Err(Error::ParseError(err_msg)) = SystemArchitecture::from_str(input) else {
582            panic!("'{input}' erroneously parsed as a SystemArchitecture")
583        };
584
585        assert_snapshot!(test_name, err_msg.to_string());
586    }
587
588    /// Make sure that invalid system architectures don't deserialize.
589    #[cfg(feature = "serde")]
590    #[rstest]
591    #[case("f oo")]
592    #[case("any")]
593    fn system_architecture_deserialize_error(#[case] input: &str) {
594        let Err(serde_json::Error { .. }) =
595            serde_json::from_str::<SystemArchitecture>(&format!("\"{input}\""))
596        else {
597            panic!("'{input}' erroneously deserialized as a SystemArchitecture")
598        };
599    }
600
601    /// Make sure that invalid and known architectures don't deserialize as unknown architectures.
602    #[cfg(feature = "serde")]
603    #[rstest]
604    #[case("f oo")]
605    #[case("x86_64")]
606    fn unknown_architecture_deserialize_error(#[case] input: &str) {
607        let Err(serde_json::Error { .. }) =
608            serde_json::from_str::<UnknownArchitecture>(&format!("\"{input}\""))
609        else {
610            panic!("'{input}' erroneously deserialized as an UnknownArchitecture")
611        };
612    }
613
614    /// Make sure that system architectures deserialize to the value they serialized from.
615    ///
616    /// Due to `SystemArchitecture::Unknown` unusual shape and our custom Deserialize logic, we do a
617    /// roundtrip check.
618    #[cfg(feature = "serde")]
619    #[rstest]
620    #[case(SystemArchitecture::X86_64V2)]
621    #[case(SystemArchitecture::Unknown(UnknownArchitecture("f_oo".to_string())))]
622    fn system_architecture_serde_roundtrip(#[case] architecture: SystemArchitecture) -> TestResult {
623        let json = serde_json::to_string(&architecture)?;
624        assert_eq!(
625            architecture,
626            serde_json::from_str::<SystemArchitecture>(&json)?
627        );
628        Ok(())
629    }
630
631    #[rstest]
632    #[case(SystemArchitecture::Aarch64, "aarch64")]
633    #[case(SystemArchitecture::from_str("f_o_o")?, "f_o_o")]
634    fn system_architecture_format_string(
635        #[case] arch: SystemArchitecture,
636        #[case] arch_str: &str,
637    ) -> TestResult {
638        assert_eq!(arch_str, format!("{arch}"));
639
640        Ok(())
641    }
642
643    /// Ensures that [`Architecture::from_str`] parses input string slices correctly.
644    #[rstest]
645    #[case("any", Architecture::Any)]
646    #[case("aarch64", SystemArchitecture::Aarch64.into())]
647    #[case("arm", SystemArchitecture::Arm.into())]
648    #[case("armv6h", SystemArchitecture::Armv6h.into())]
649    #[case("armv7h", SystemArchitecture::Armv7h.into())]
650    #[case("i386", SystemArchitecture::I386.into())]
651    #[case("i486", SystemArchitecture::I486.into())]
652    #[case("i686", SystemArchitecture::I686.into())]
653    #[case("loong64", SystemArchitecture::Loong64.into())]
654    #[case("pentium4", SystemArchitecture::Pentium4.into())]
655    #[case("riscv32", SystemArchitecture::Riscv32.into())]
656    #[case("riscv64", SystemArchitecture::Riscv64.into())]
657    #[case("x86_64", SystemArchitecture::X86_64.into())]
658    #[case("x86_64_v2", SystemArchitecture::X86_64V2.into())]
659    #[case("x86_64_v3", SystemArchitecture::X86_64V3.into())]
660    #[case("x86_64_v4", SystemArchitecture::X86_64V4.into())]
661    #[case("foo", UnknownArchitecture("foo".to_string()).into())]
662    #[case("f_oo", UnknownArchitecture("f_oo".to_string()).into())]
663    fn architecture_from_string(#[case] input: &str, #[case] expected: Architecture) -> TestResult {
664        assert_eq!(Architecture::from_str(input)?, expected);
665
666        Ok(())
667    }
668
669    #[rstest]
670    #[case("f oo")]
671    fn invalid_architecture_from_string(#[case] input: &str) {
672        let (test_name, _guard) = configure_insta();
673
674        let Err(Error::ParseError(err_msg)) = Architecture::from_str(input) else {
675            panic!("'{input}' erroneously parsed as a Architecture")
676        };
677
678        assert_snapshot!(test_name, err_msg.to_string());
679    }
680
681    #[rstest]
682    #[case(Architecture::Any, "any")]
683    #[case(SystemArchitecture::Aarch64.into(), "aarch64")]
684    #[case(Architecture::from_str("foo")?, "foo")]
685    fn architecture_format_string(
686        #[case] arch: Architecture,
687        #[case] arch_str: &str,
688    ) -> TestResult {
689        assert_eq!(arch_str, format!("{arch}"));
690
691        Ok(())
692    }
693
694    /// Ensures that [`Architectures::try_from`] succeeds for a [`Vec`] of [`Architecture`].
695    #[rstest]
696    #[case(vec![Architecture::Any], Architectures::Any)]
697    #[case(
698        vec![SystemArchitecture::Aarch64.into()],
699        Architectures::Some(vec![SystemArchitecture::Aarch64])
700    )]
701    #[case(
702        vec![SystemArchitecture::Arm.into(), SystemArchitecture::I386.into()],
703        Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386])
704    )]
705    // Duplicates are allowed (discouraged by linter)
706    #[case(
707        vec![SystemArchitecture::Arm.into(), SystemArchitecture::Arm.into()],
708        Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::Arm])
709    )]
710    #[case(vec![], Architectures::Some(vec![]))]
711    fn architectures_try_from_vec_succeeds(
712        #[case] archs: Vec<Architecture>,
713        #[case] expected: Architectures,
714    ) -> TestResult {
715        assert_eq!(Architectures::try_from(archs)?, expected);
716
717        Ok(())
718    }
719
720    /// Ensures that [`Architectures::try_from`] fails on a [`Vec`] of incompatible
721    /// [`Architecture`].
722    #[rstest]
723    #[case(vec![Architecture::Any, SystemArchitecture::I386.into()])]
724    #[case(vec![Architecture::Any, Architecture::Any])]
725    fn architectures_from_vec(#[case] archs: Vec<Architecture>) -> TestResult {
726        let Err(Error::InvalidArchitectures {
727            architectures: _archs,
728            ..
729        }) = Architectures::try_from(archs)
730        else {
731            panic!("Expected to fail with Error::InvalidArchitectures");
732        };
733
734        Ok(())
735    }
736
737    #[rstest]
738    #[case(Architectures::Any, "any")]
739    #[case(Architectures::Some(vec![SystemArchitecture::Aarch64]), "aarch64")]
740    #[case(Architectures::Some(vec![SystemArchitecture::Arm, SystemArchitecture::I386]), "arm, i386")]
741    #[case(Architectures::Some(vec![]), "")]
742    fn architectures_format_display(#[case] archs: Architectures, #[case] archs_str: &str) {
743        assert_eq!(archs_str, format!("{archs}"));
744    }
745
746    #[rstest]
747    #[case("32", Ok(ElfArchitectureFormat::Bit32))]
748    #[case("64", Ok(ElfArchitectureFormat::Bit64))]
749    #[case("foo", Err(ParseError::VariantNotFound))]
750    fn elf_architecture_format_from_string(
751        #[case] s: &str,
752        #[case] arch: Result<ElfArchitectureFormat, ParseError>,
753    ) {
754        assert_eq!(ElfArchitectureFormat::from_str(s), arch);
755    }
756
757    #[rstest]
758    #[case(ElfArchitectureFormat::Bit32, "32")]
759    #[case(ElfArchitectureFormat::Bit64, "64")]
760    fn elf_architecture_format_display(
761        #[case] arch: ElfArchitectureFormat,
762        #[case] arch_str: &str,
763    ) {
764        assert_eq!(arch_str, format!("{arch}"));
765    }
766}