Skip to main content

cargo_config2/
error.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use alloc::{
4    boxed::Box,
5    format,
6    string::{String, ToString as _},
7};
8use core::fmt;
9use std::{ffi::OsString, io};
10
11macro_rules! format_err {
12    ($($tt:tt)*) => {
13        crate::Error::new(alloc::format!($($tt)*))
14    };
15}
16
17macro_rules! bail {
18    ($($tt:tt)*) => {
19        return Err(format_err!($($tt)*))
20    };
21}
22
23pub(crate) type Result<T, E = Error> = core::result::Result<T, E>;
24
25/// An error that occurred during loading or resolving the Cargo configuration.
26#[derive(Debug)]
27pub struct Error(ErrorKind);
28
29// Hiding error variants from a library's public error type to prevent
30// dependency updates from becoming breaking changes.
31// We can add `is_*` methods that indicate the kind of error if needed, but
32// don't expose dependencies' types directly in the public API.
33#[derive(Debug)]
34pub(crate) enum ErrorKind {
35    Io(io::Error),
36
37    CfgExprParse(crate::cfg_expr::error::ParseError),
38
39    Other(Box<str>),
40    WithContext(Box<str>, Option<Box<dyn std::error::Error + Send + Sync>>),
41}
42
43impl Error {
44    pub(crate) fn new(e: impl Into<ErrorKind>) -> Self {
45        Self(e.into())
46    }
47
48    pub(crate) fn env_not_unicode(name: &str, var: OsString) -> Self {
49        Self(ErrorKind::WithContext(
50            format!("failed to parse environment variable `{name}`").into_boxed_str(),
51            Some(Box::new(std::env::VarError::NotUnicode(var))),
52        ))
53    }
54    pub(crate) fn env_not_unicode_redacted(name: &str) -> Self {
55        Self(ErrorKind::WithContext(
56            format!("failed to parse environment variable `{name}`").into_boxed_str(),
57            Some("environment variable was not valid unicode: [REDACTED]".into()),
58        ))
59    }
60}
61
62impl fmt::Display for Error {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match &self.0 {
65            ErrorKind::Io(e) => fmt::Display::fmt(e, f),
66            ErrorKind::CfgExprParse(e) => fmt::Display::fmt(e, f),
67            ErrorKind::Other(e) | ErrorKind::WithContext(e, ..) => fmt::Display::fmt(e, f),
68        }
69    }
70}
71
72impl std::error::Error for Error {
73    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
74        match &self.0 {
75            ErrorKind::Io(e) => e.source(),
76            ErrorKind::CfgExprParse(e) => e.source(),
77            ErrorKind::Other(_) => None,
78            ErrorKind::WithContext(_, e) => Some(&**e.as_ref()?),
79        }
80    }
81}
82
83// TODO: consider removing this in the next breaking release
84impl From<Error> for io::Error {
85    fn from(e: Error) -> Self {
86        match e.0 {
87            ErrorKind::Io(e) => e,
88            ErrorKind::CfgExprParse(e) => Self::other(e),
89            ErrorKind::Other(e) | ErrorKind::WithContext(e, None) => Self::other(e.into_string()),
90            ErrorKind::WithContext(msg, Some(source)) => {
91                let kind = if let Some(e) = source.downcast_ref::<io::Error>() {
92                    e.kind()
93                } else if source.downcast_ref::<toml::de::Error>().is_some() {
94                    io::ErrorKind::InvalidData
95                } else {
96                    io::ErrorKind::Other
97                };
98                Self::new(kind, Error(ErrorKind::WithContext(msg, Some(source))))
99            }
100        }
101    }
102}
103
104impl From<String> for ErrorKind {
105    fn from(s: String) -> Self {
106        Self::Other(s.into_boxed_str())
107    }
108}
109impl From<crate::cfg_expr::error::ParseError> for ErrorKind {
110    fn from(e: crate::cfg_expr::error::ParseError) -> Self {
111        Self::CfgExprParse(e)
112    }
113}
114
115// Note: Do not implement From<ThirdPartyErrorType> to prevent dependency
116// updates from becoming breaking changes.
117// Implementing `From<StdErrorType>` should also be avoided whenever possible,
118// as it would be a breaking change to remove the implementation if the
119// conversion is no longer needed due to changes in the internal implementation.
120// TODO(semver): consider removing them in the next breaking release
121impl From<io::Error> for Error {
122    fn from(e: io::Error) -> Self {
123        Self(ErrorKind::Io(e))
124    }
125}
126// TODO(semver): this is no longer used in our code; remove in the next breaking release
127impl From<std::env::VarError> for Error {
128    fn from(e: std::env::VarError) -> Self {
129        Self(ErrorKind::Other(e.to_string().into_boxed_str()))
130    }
131}
132
133// Inspired by anyhow::Context.
134pub(crate) trait Context<T, E> {
135    fn context<C>(self, context: C) -> Result<T, Error>
136    where
137        C: fmt::Display;
138    fn with_context<C, F>(self, context: F) -> Result<T, Error>
139    where
140        C: fmt::Display,
141        F: FnOnce() -> C;
142}
143impl<T, E> Context<T, E> for Result<T, E>
144where
145    E: std::error::Error + Send + Sync + 'static,
146{
147    fn context<C>(self, context: C) -> Result<T, Error>
148    where
149        C: fmt::Display,
150    {
151        match self {
152            Ok(ok) => Ok(ok),
153            Err(e) => Err(Error(ErrorKind::WithContext(
154                context.to_string().into_boxed_str(),
155                Some(Box::new(e)),
156            ))),
157        }
158    }
159    fn with_context<C, F>(self, context: F) -> Result<T, Error>
160    where
161        C: fmt::Display,
162        F: FnOnce() -> C,
163    {
164        match self {
165            Ok(ok) => Ok(ok),
166            Err(e) => Err(Error(ErrorKind::WithContext(
167                context().to_string().into_boxed_str(),
168                Some(Box::new(e)),
169            ))),
170        }
171    }
172}
173impl<T> Context<T, core::convert::Infallible> for Option<T> {
174    fn context<C>(self, context: C) -> Result<T, Error>
175    where
176        C: fmt::Display,
177    {
178        match self {
179            Some(ok) => Ok(ok),
180            None => Err(Error(ErrorKind::WithContext(context.to_string().into_boxed_str(), None))),
181        }
182    }
183    fn with_context<C, F>(self, context: F) -> Result<T, Error>
184    where
185        C: fmt::Display,
186        F: FnOnce() -> C,
187    {
188        match self {
189            Some(ok) => Ok(ok),
190            None => {
191                Err(Error(ErrorKind::WithContext(context().to_string().into_boxed_str(), None)))
192            }
193        }
194    }
195}