1use thiserror::Error;
7
8#[derive(Error, Debug)]
13pub enum CargoError {
14 #[error("Failed to parse Cargo.toml: {message}")]
16 TomlParseError { message: String },
17
18 #[error("Invalid semver version specifier '{specifier}': {message}")]
20 InvalidVersionSpecifier { specifier: String, message: String },
21
22 #[error("Crate '{package}' not found on crates.io")]
24 PackageNotFound { package: String },
25
26 #[error("crates.io registry request failed for '{package}': {source}")]
28 RegistryError {
29 package: String,
30 #[source]
31 source: Box<dyn std::error::Error + Send + Sync>,
32 },
33
34 #[error("Failed to parse crates.io API response for '{package}': {source}")]
36 ApiResponseError {
37 package: String,
38 #[source]
39 source: serde_json::Error,
40 },
41
42 #[error("Invalid Cargo.toml structure: {message}")]
44 InvalidStructure { message: String },
45
46 #[error("Missing required field '{field}' in {section}")]
48 MissingField { section: String, field: String },
49
50 #[error("Workspace error: {message}")]
52 WorkspaceError { message: String },
53
54 #[error("Invalid file URI: {uri}")]
56 InvalidUri { uri: String },
57
58 #[error("Cache error: {0}")]
60 CacheError(String),
61
62 #[error("I/O error: {0}")]
64 Io(#[from] std::io::Error),
65
66 #[error(transparent)]
68 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
69}
70
71pub type Result<T> = std::result::Result<T, CargoError>;
73
74impl CargoError {
75 pub fn registry_error(
77 package: impl Into<String>,
78 error: impl std::error::Error + Send + Sync + 'static,
79 ) -> Self {
80 Self::RegistryError {
81 package: package.into(),
82 source: Box::new(error),
83 }
84 }
85
86 pub fn api_response_error(package: impl Into<String>, error: serde_json::Error) -> Self {
88 Self::ApiResponseError {
89 package: package.into(),
90 source: error,
91 }
92 }
93
94 pub fn invalid_structure(message: impl Into<String>) -> Self {
96 Self::InvalidStructure {
97 message: message.into(),
98 }
99 }
100
101 pub fn missing_field(section: impl Into<String>, field: impl Into<String>) -> Self {
103 Self::MissingField {
104 section: section.into(),
105 field: field.into(),
106 }
107 }
108
109 pub fn invalid_version_specifier(
111 specifier: impl Into<String>,
112 message: impl Into<String>,
113 ) -> Self {
114 Self::InvalidVersionSpecifier {
115 specifier: specifier.into(),
116 message: message.into(),
117 }
118 }
119
120 pub fn workspace_error(message: impl Into<String>) -> Self {
122 Self::WorkspaceError {
123 message: message.into(),
124 }
125 }
126
127 pub fn invalid_uri(uri: impl Into<String>) -> Self {
129 Self::InvalidUri { uri: uri.into() }
130 }
131}
132
133impl From<deps_core::DepsError> for CargoError {
135 fn from(err: deps_core::DepsError) -> Self {
136 match err {
137 deps_core::DepsError::ParseError { source, .. } => Self::CacheError(source.to_string()),
138 deps_core::DepsError::CacheError(msg) => Self::CacheError(msg),
139 deps_core::DepsError::InvalidVersionReq(msg) => Self::InvalidVersionSpecifier {
140 specifier: String::new(),
141 message: msg,
142 },
143 deps_core::DepsError::Io(e) => Self::Io(e),
144 deps_core::DepsError::Json(e) => Self::ApiResponseError {
145 package: String::new(),
146 source: e,
147 },
148 other => Self::CacheError(other.to_string()),
149 }
150 }
151}
152
153impl From<CargoError> for deps_core::DepsError {
155 fn from(err: CargoError) -> Self {
156 match err {
157 CargoError::TomlParseError { message } => Self::ParseError {
158 file_type: "Cargo.toml".into(),
159 source: Box::new(std::io::Error::other(message)),
160 },
161 CargoError::InvalidVersionSpecifier { message, .. } => Self::InvalidVersionReq(message),
162 CargoError::PackageNotFound { package } => {
163 Self::CacheError(format!("Crate '{package}' not found"))
164 }
165 CargoError::RegistryError { package, source } => Self::ParseError {
166 file_type: format!("crates.io registry for {package}"),
167 source,
168 },
169 CargoError::ApiResponseError { source, .. } => Self::Json(source),
170 CargoError::InvalidStructure { message } => Self::CacheError(message),
171 CargoError::MissingField { section, field } => {
172 Self::CacheError(format!("Missing '{field}' in {section}"))
173 }
174 CargoError::WorkspaceError { message } => Self::CacheError(message),
175 CargoError::InvalidUri { uri } => Self::CacheError(format!("Invalid URI: {uri}")),
176 CargoError::CacheError(msg) => Self::CacheError(msg),
177 CargoError::Io(e) => Self::Io(e),
178 CargoError::Other(e) => Self::CacheError(e.to_string()),
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_error_display() {
189 let err = CargoError::PackageNotFound {
190 package: "nonexistent".into(),
191 };
192 assert_eq!(
193 err.to_string(),
194 "Crate 'nonexistent' not found on crates.io"
195 );
196
197 let err = CargoError::missing_field("dependencies", "serde");
198 assert_eq!(
199 err.to_string(),
200 "Missing required field 'serde' in dependencies"
201 );
202
203 let err = CargoError::invalid_structure("missing [package] section");
204 assert_eq!(
205 err.to_string(),
206 "Invalid Cargo.toml structure: missing [package] section"
207 );
208 }
209
210 #[test]
211 fn test_error_construction() {
212 let err =
213 CargoError::registry_error("serde", std::io::Error::from(std::io::ErrorKind::NotFound));
214 assert!(matches!(err, CargoError::RegistryError { .. }));
215
216 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
217 let err = CargoError::api_response_error("tokio", json_err);
218 assert!(matches!(err, CargoError::ApiResponseError { .. }));
219 }
220
221 #[test]
222 fn test_invalid_version_specifier() {
223 let err = CargoError::invalid_version_specifier("invalid", "not a valid semver");
224 assert!(err.to_string().contains("invalid"));
225 assert!(err.to_string().contains("not a valid semver"));
226 }
227
228 #[test]
229 fn test_workspace_error() {
230 let err = CargoError::workspace_error("workspace root not found");
231 assert!(err.to_string().contains("workspace root not found"));
232 }
233
234 #[test]
235 fn test_invalid_uri() {
236 let err = CargoError::invalid_uri("not-a-valid-uri");
237 assert!(err.to_string().contains("not-a-valid-uri"));
238 }
239
240 #[test]
241 fn test_conversion_to_deps_error() {
242 let cargo_err = CargoError::PackageNotFound {
243 package: "test".into(),
244 };
245 let deps_err: deps_core::DepsError = cargo_err.into();
246 assert!(deps_err.to_string().contains("not found"));
247 }
248}