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#[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 Aarch64,
62 Arm,
64 Armv6h,
66 Armv7h,
68 I386,
70 I486,
72 I686,
74 Loong64,
76 Pentium4,
78 Riscv32,
80 Riscv64,
82 X86_64,
84 #[strum(to_string = "x86_64_v2")]
86 #[cfg_attr(feature = "serde", serde(rename = "x86_64_v2"))]
87 X86_64V2,
88 #[strum(to_string = "x86_64_v3")]
90 #[cfg_attr(feature = "serde", serde(rename = "x86_64_v3"))]
91 X86_64V3,
92 #[strum(to_string = "x86_64_v4")]
94 #[cfg_attr(feature = "serde", serde(rename = "x86_64_v4"))]
95 X86_64V4,
96 #[strum(transparent)]
98 #[cfg_attr(feature = "serde", serde(untagged))]
99 Unknown(UnknownArchitecture),
100}
101
102impl AlpmParser for SystemArchitecture {
103 fn parser(input: &mut &str) -> ModalResult<SystemArchitecture> {
109 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 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 let architecture = match architecture.as_str() {
133 "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 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 fn from_str(s: &str) -> Result<SystemArchitecture, Self::Err> {
181 Ok(Self::parser_until_eof.parse(s)?)
182 }
183}
184
185#[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 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 pub fn inner(&self) -> &str {
219 &self.0
220 }
221}
222
223impl From<UnknownArchitecture> for SystemArchitecture {
224 fn from(value: UnknownArchitecture) -> Self {
226 SystemArchitecture::Unknown(value)
227 }
228}
229
230impl From<UnknownArchitecture> for Architecture {
231 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#[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,
290 #[strum(transparent)]
292 #[cfg_attr(feature = "serde", serde(untagged))]
293 Some(SystemArchitecture),
294}
295
296impl AlpmParser for Architecture {
297 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 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 fn from(value: SystemArchitecture) -> Self {
343 Architecture::Some(value)
344 }
345}
346
347#[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,
362 #[strum(transparent)]
364 #[cfg_attr(feature = "serde", serde(untagged))]
365 Some(Vec<SystemArchitecture>),
366}
367
368impl Architectures {
369 pub fn len(&self) -> usize {
371 match self {
372 Architectures::Any => 1,
373 Architectures::Some(archs) => archs.len(),
374 }
375 }
376
377 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 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 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 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 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 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 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 fn try_from(value: Vec<Architecture>) -> Result<Self, Self::Error> {
483 value.iter().collect::<Vec<&Architecture>>().try_into()
484 }
485}
486
487#[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 #[strum(to_string = "32")]
523 Bit32 = 32,
524 #[strum(to_string = "64")]
526 Bit64 = 64,
527}
528
529impl AlpmParser for ElfArchitectureFormat {
530 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 #[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 #[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 #[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 #[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}