cargo_credential/
error.rs
1use serde::{Deserialize, Serialize};
2use std::error::Error as StdError;
3use thiserror::Error as ThisError;
4
5#[derive(Serialize, Deserialize, ThisError, Debug)]
13#[serde(rename_all = "kebab-case", tag = "kind")]
14#[non_exhaustive]
15pub enum Error {
16 #[error("registry not supported")]
20 UrlNotSupported,
21
22 #[error("credential not found")]
25 NotFound,
26
27 #[error("requested operation not supported")]
30 OperationNotSupported,
31
32 #[error(transparent)]
35 #[serde(with = "error_serialize")]
36 Other(Box<dyn StdError + Sync + Send>),
37
38 #[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#[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
98mod 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 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 let e = anyhow::anyhow!("E1").context("E2").context("E3");
176 let s1 = format!("{:?}", e);
178 let e: Error = e.into();
180 let json = serde_json::to_string_pretty(&e).unwrap();
182 let e: anyhow::Error = e.into();
184 let s2 = format!("{:?}", e);
185 assert_eq!(s1, s2);
186
187 let e: Error = serde_json::from_str(&json).unwrap();
189 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}