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