cargo_credential/
error.rs

1use serde::{Deserialize, Serialize};
2use std::error::Error as StdError;
3use thiserror::Error as ThisError;
4
5/// Credential provider error type.
6///
7/// `UrlNotSupported` and `NotFound` errors both cause Cargo
8/// to attempt another provider, if one is available. The other
9/// variants are fatal.
10///
11/// Note: Do not add a tuple variant, as it cannot be serialized.
12#[derive(Serialize, Deserialize, ThisError, Debug)]
13#[serde(rename_all = "kebab-case", tag = "kind")]
14#[non_exhaustive]
15pub enum Error {
16    /// Registry URL is not supported. This should be used if
17    /// the provider only works for some registries. Cargo will
18    /// try another provider, if available
19    #[error("registry not supported")]
20    UrlNotSupported,
21
22    /// Credentials could not be found. Cargo will try another
23    /// provider, if available
24    #[error("credential not found")]
25    NotFound,
26
27    /// The provider doesn't support this operation, such as
28    /// a provider that can't support 'login' / 'logout'
29    #[error("requested operation not supported")]
30    OperationNotSupported,
31
32    /// The provider failed to perform the operation. Other
33    /// providers will not be attempted
34    #[error(transparent)]
35    #[serde(with = "error_serialize")]
36    Other(Box<dyn StdError + Sync + Send>),
37
38    /// A new variant was added to this enum since Cargo was built
39    #[error("unknown error kind; try updating Cargo?")]
40    #[serde(other)]
41    Unknown,
42}
43
44impl From<String> for Error {
45    fn from(message: String) -> Self {
46        Box::new(StringTypedError {
47            message,
48            source: None,
49        })
50        .into()
51    }
52}
53
54impl From<&str> for Error {
55    fn from(err: &str) -> Self {
56        err.to_string().into()
57    }
58}
59
60impl From<anyhow::Error> for Error {
61    fn from(value: anyhow::Error) -> Self {
62        let mut prev = None;
63        for e in value.chain().rev() {
64            prev = Some(Box::new(StringTypedError {
65                message: e.to_string(),
66                source: prev,
67            }));
68        }
69        Error::Other(prev.unwrap())
70    }
71}
72
73impl<T: StdError + Send + Sync + 'static> From<Box<T>> for Error {
74    fn from(value: Box<T>) -> Self {
75        Error::Other(value)
76    }
77}
78
79/// String-based error type with an optional source
80#[derive(Debug)]
81struct StringTypedError {
82    message: String,
83    source: Option<Box<StringTypedError>>,
84}
85
86impl StdError for StringTypedError {
87    fn source(&self) -> Option<&(dyn StdError + 'static)> {
88        self.source.as_ref().map(|err| err as &dyn StdError)
89    }
90}
91
92impl std::fmt::Display for StringTypedError {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        self.message.fmt(f)
95    }
96}
97
98/// Serializer / deserializer for any boxed error.
99/// The string representation of the error, and its `source` chain can roundtrip across
100/// the serialization. The actual types are lost (downcast will not work).
101mod error_serialize {
102    use std::error::Error as StdError;
103    use std::ops::Deref;
104
105    use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serializer};
106
107    use crate::error::StringTypedError;
108
109    pub fn serialize<S>(
110        e: &Box<dyn StdError + Send + Sync>,
111        serializer: S,
112    ) -> Result<S::Ok, S::Error>
113    where
114        S: Serializer,
115    {
116        let mut state = serializer.serialize_struct("StringTypedError", 2)?;
117        state.serialize_field("message", &format!("{}", e))?;
118
119        // Serialize the source error chain recursively
120        let mut current_source: &dyn StdError = e.deref();
121        let mut sources = Vec::new();
122        while let Some(err) = current_source.source() {
123            sources.push(err.to_string());
124            current_source = err;
125        }
126        state.serialize_field("caused-by", &sources)?;
127        state.end()
128    }
129
130    pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<dyn StdError + Sync + Send>, D::Error>
131    where
132        D: Deserializer<'de>,
133    {
134        #[derive(Deserialize)]
135        #[serde(rename_all = "kebab-case")]
136        struct ErrorData {
137            message: String,
138            caused_by: Option<Vec<String>>,
139        }
140        let data = ErrorData::deserialize(deserializer)?;
141        let mut prev = None;
142        if let Some(source) = data.caused_by {
143            for e in source.into_iter().rev() {
144                prev = Some(Box::new(StringTypedError {
145                    message: e,
146                    source: prev,
147                }));
148            }
149        }
150        let e = Box::new(StringTypedError {
151            message: data.message,
152            source: prev,
153        });
154        Ok(e)
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::Error;
161
162    #[test]
163    pub fn unknown_kind() {
164        let json = r#"{
165            "kind": "unexpected-kind",
166            "unexpected-content": "test"
167          }"#;
168        let e: Error = serde_json::from_str(&json).unwrap();
169        assert!(matches!(e, Error::Unknown));
170    }
171
172    #[test]
173    pub fn roundtrip() {
174        // Construct an error with context
175        let e = anyhow::anyhow!("E1").context("E2").context("E3");
176        // Convert to a string with contexts.
177        let s1 = format!("{:?}", e);
178        // Convert the error into an `Error`
179        let e: Error = e.into();
180        // Convert that error into JSON
181        let json = serde_json::to_string_pretty(&e).unwrap();
182        // Convert that error back to anyhow
183        let e: anyhow::Error = e.into();
184        let s2 = format!("{:?}", e);
185        assert_eq!(s1, s2);
186
187        // Convert the error back from JSON
188        let e: Error = serde_json::from_str(&json).unwrap();
189        // Convert to back to anyhow
190        let e: anyhow::Error = e.into();
191        let s3 = format!("{:?}", e);
192        assert_eq!(s2, s3);
193
194        assert_eq!(
195            r#"{
196  "kind": "other",
197  "message": "E3",
198  "caused-by": [
199    "E2",
200    "E1"
201  ]
202}"#,
203            json
204        );
205    }
206}