rust_toolchain_file/
lib.rs

1//! Modelled after docs published at: <https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file>
2
3// exports
4pub use {legacy::LegacyToolchainFile, toml::RustToolchainToml};
5
6pub mod legacy;
7pub mod toml;
8
9#[cfg(test)]
10mod tests;
11
12/// Model of a Rust toolchain file, which can be used to pin a specific toolchain to a Rust project.
13#[derive(Debug, PartialEq)]
14pub enum ToolchainFile {
15    /// The legacy variant of the toolchain file only specifies the name of a toolchain
16    Legacy(LegacyToolchainFile),
17    /// A specification of a toolchain file, which builds on top of the TOML format.
18    Toml(RustToolchainToml),
19}
20
21/// Variants which may be used to identify supported rust-toolchain variants.
22#[derive(Debug, Copy, Clone, PartialEq)]
23pub enum Variant {
24    Legacy,
25    Toml,
26}
27
28impl Variant {
29    fn parse_with(&self, content: &str) -> Result<ToolchainFile, ParserError> {
30        match *self {
31            Self::Legacy => legacy::Parser::new(content)
32                .parse()
33                .map(ToolchainFile::Legacy)
34                .map_err(From::from),
35            Self::Toml => toml::Parser::new(content)
36                .parse()
37                .map(ToolchainFile::Toml)
38                .map_err(From::from),
39        }
40    }
41}
42
43/// Option to determine whether only to parse one rust-toolchain variant (TOML, or legacy), or
44/// to fallback to another variant.
45#[derive(Debug, Copy, Clone, PartialEq)]
46pub enum ParseStrategy {
47    Only(Variant),
48    Fallback {
49        first: Variant,
50        fallback_to: Variant,
51    },
52}
53
54/// A combined parser for the legacy and TOML toolchain file formats.
55pub struct Parser<'content> {
56    content: &'content str,
57
58    parse_option: ParseStrategy,
59}
60
61impl<'content> Parser<'content> {
62    pub fn new(content: &'content str, parse_option: ParseStrategy) -> Self {
63        Self {
64            content,
65            parse_option,
66        }
67    }
68}
69
70impl Parser<'_> {
71    pub fn parse(&self) -> Result<ToolchainFile, ParserError> {
72        match self.parse_option {
73            ParseStrategy::Only(v) => v.parse_with(self.content),
74            ParseStrategy::Fallback { first, fallback_to } => {
75                first.parse_with(self.content).or_else(|original_err| {
76                    fallback_to
77                        .parse_with(self.content)
78                        .map_err(|fallback_err| {
79                            ParserError::FallbackError(FallbackError {
80                                first: Box::new(original_err),
81                                fallback_to: Box::new(fallback_err),
82                            })
83                        })
84                })
85            }
86        }
87    }
88}
89
90#[derive(Debug, thiserror::Error, PartialEq)]
91pub enum ParserError {
92    #[error("Failed to parse legacy toolchain-file variant: {0}")]
93    LegacyParseError(#[from] legacy::ParserError),
94
95    #[error("Failed to parse TOML toolchain-file variant: {0}")]
96    TomlParseError(#[from] toml::ParserError),
97
98    #[error("Both original and fallback parse attempts failed: {0}")]
99    FallbackError(FallbackError),
100}
101
102#[derive(Debug, PartialEq, thiserror::Error)]
103#[error("Failed to parse: '{first}' and failed to fallback on '{fallback_to}'")]
104pub struct FallbackError {
105    first: Box<ParserError>,
106    fallback_to: Box<ParserError>,
107}
108
109impl FallbackError {
110    pub fn first(&self) -> &ParserError {
111        self.first.as_ref()
112    }
113
114    pub fn fallback_to(&self) -> &ParserError {
115        self.fallback_to.as_ref()
116    }
117}