Skip to main content

alpm_types/
system.rs

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