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