Skip to main content

alpm_types/
system.rs

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