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#[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 Aarch64,
60 Arm,
62 Armv6h,
64 Armv7h,
66 I386,
68 I486,
70 I686,
72 Loong64,
74 Pentium4,
76 Riscv32,
78 Riscv64,
80 X86_64,
82 #[strum(to_string = "x86_64_v2")]
84 #[cfg_attr(feature = "serde", serde(rename = "x86_64_v2"))]
85 X86_64V2,
86 #[strum(to_string = "x86_64_v3")]
88 #[cfg_attr(feature = "serde", serde(rename = "x86_64_v3"))]
89 X86_64V3,
90 #[strum(to_string = "x86_64_v4")]
92 #[cfg_attr(feature = "serde", serde(rename = "x86_64_v4"))]
93 X86_64V4,
94 #[strum(transparent)]
96 #[cfg_attr(feature = "serde", serde(untagged))]
97 Unknown(UnknownArchitecture),
98}
99
100impl AlpmParser for SystemArchitecture {
101 fn parser<'a>(input: &mut Input<'a>) -> PResult<'a, Self> {
107 let parser = move |input: &mut Input<'a>| -> PResult<'a, Self> {
108 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 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 let architecture = match architecture.as_str() {
130 "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 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 fn from_str(s: &str) -> Result<SystemArchitecture, Self::Err> {
182 Ok(Self::parser_until_eof.parse(Input::new(s))?)
183 }
184}
185
186#[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 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 fn from(value: UnknownArchitecture) -> Self {
220 SystemArchitecture::Unknown(value)
221 }
222}
223
224impl From<UnknownArchitecture> for Architecture {
225 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#[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,
284 #[strum(transparent)]
286 #[cfg_attr(feature = "serde", serde(untagged))]
287 Some(SystemArchitecture),
288}
289
290impl AlpmParser for Architecture {
291 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 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 fn from(value: SystemArchitecture) -> Self {
339 Architecture::Some(value)
340 }
341}
342
343#[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,
358 #[strum(transparent)]
360 #[cfg_attr(feature = "serde", serde(untagged))]
361 Some(Vec<SystemArchitecture>),
362}
363
364impl Architectures {
365 pub fn len(&self) -> usize {
367 match self {
368 Architectures::Any => 1,
369 Architectures::Some(archs) => archs.len(),
370 }
371 }
372
373 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 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 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 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 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 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 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 fn try_from(value: Vec<Architecture>) -> Result<Self, Self::Error> {
479 value.iter().collect::<Vec<&Architecture>>().try_into()
480 }
481}
482
483#[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 #[strum(to_string = "32")]
519 Bit32 = 32,
520 #[strum(to_string = "64")]
522 Bit64 = 64,
523}
524
525impl AlpmParser for ElfArchitectureFormat {
526 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}