1use thiserror::Error;
7
8#[derive(Error, Debug)]
13pub enum NpmError {
14 #[error("Failed to parse package.json: {source}")]
16 JsonParseError {
17 #[source]
18 source: serde_json::Error,
19 },
20
21 #[error("Invalid npm semver version specifier '{specifier}': {message}")]
23 InvalidVersionSpecifier { specifier: String, message: String },
24
25 #[error("Package '{package}' not found on npm registry")]
27 PackageNotFound { package: String },
28
29 #[error("npm 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 npm API response for '{package}': {source}")]
39 ApiResponseError {
40 package: String,
41 #[source]
42 source: serde_json::Error,
43 },
44
45 #[error("Invalid package.json structure: {message}")]
47 InvalidStructure { message: String },
48
49 #[error("Missing required field '{field}' in {section}")]
51 MissingField { section: String, field: String },
52
53 #[error("Cache error: {0}")]
55 CacheError(String),
56
57 #[error("I/O error: {0}")]
59 Io(#[from] std::io::Error),
60
61 #[error(transparent)]
63 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
64}
65
66pub type Result<T> = std::result::Result<T, NpmError>;
68
69impl NpmError {
70 pub fn registry_error(
72 package: impl Into<String>,
73 error: impl std::error::Error + Send + Sync + 'static,
74 ) -> Self {
75 Self::RegistryError {
76 package: package.into(),
77 source: Box::new(error),
78 }
79 }
80
81 pub fn api_response_error(package: impl Into<String>, error: serde_json::Error) -> Self {
83 Self::ApiResponseError {
84 package: package.into(),
85 source: error,
86 }
87 }
88
89 pub fn invalid_structure(message: impl Into<String>) -> Self {
91 Self::InvalidStructure {
92 message: message.into(),
93 }
94 }
95
96 pub fn missing_field(section: impl Into<String>, field: impl Into<String>) -> Self {
98 Self::MissingField {
99 section: section.into(),
100 field: field.into(),
101 }
102 }
103
104 pub fn invalid_version_specifier(
106 specifier: impl Into<String>,
107 message: impl Into<String>,
108 ) -> Self {
109 Self::InvalidVersionSpecifier {
110 specifier: specifier.into(),
111 message: message.into(),
112 }
113 }
114}
115
116impl From<deps_core::DepsError> for NpmError {
118 fn from(err: deps_core::DepsError) -> Self {
119 match err {
120 deps_core::DepsError::ParseError { source, .. } => Self::CacheError(source.to_string()),
121 deps_core::DepsError::CacheError(msg) => Self::CacheError(msg),
122 deps_core::DepsError::InvalidVersionReq(msg) => Self::InvalidVersionSpecifier {
123 specifier: String::new(),
124 message: msg,
125 },
126 deps_core::DepsError::Io(e) => Self::Io(e),
127 deps_core::DepsError::Json(e) => Self::JsonParseError { source: e },
128 other => Self::CacheError(other.to_string()),
129 }
130 }
131}
132
133impl From<NpmError> for deps_core::DepsError {
135 fn from(err: NpmError) -> Self {
136 match err {
137 NpmError::JsonParseError { source } => Self::Json(source),
138 NpmError::InvalidVersionSpecifier { message, .. } => Self::InvalidVersionReq(message),
139 NpmError::PackageNotFound { package } => {
140 Self::CacheError(format!("Package '{package}' not found"))
141 }
142 NpmError::RegistryError { package, source } => Self::ParseError {
143 file_type: format!("npm registry for {package}"),
144 source,
145 },
146 NpmError::ApiResponseError { source, .. } => Self::Json(source),
147 NpmError::InvalidStructure { message } => Self::CacheError(message),
148 NpmError::MissingField { section, field } => {
149 Self::CacheError(format!("Missing '{field}' in {section}"))
150 }
151 NpmError::CacheError(msg) => Self::CacheError(msg),
152 NpmError::Io(e) => Self::Io(e),
153 NpmError::Other(e) => Self::CacheError(e.to_string()),
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_error_display() {
164 let err = NpmError::PackageNotFound {
165 package: "nonexistent".into(),
166 };
167 assert_eq!(
168 err.to_string(),
169 "Package 'nonexistent' not found on npm registry"
170 );
171
172 let err = NpmError::missing_field("dependencies", "express");
173 assert_eq!(
174 err.to_string(),
175 "Missing required field 'express' in dependencies"
176 );
177
178 let err = NpmError::invalid_structure("missing name field");
179 assert_eq!(
180 err.to_string(),
181 "Invalid package.json structure: missing name field"
182 );
183 }
184
185 #[test]
186 fn test_error_construction() {
187 let err = NpmError::registry_error(
188 "express",
189 std::io::Error::from(std::io::ErrorKind::NotFound),
190 );
191 assert!(matches!(err, NpmError::RegistryError { .. }));
192
193 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
194 let err = NpmError::api_response_error("lodash", json_err);
195 assert!(matches!(err, NpmError::ApiResponseError { .. }));
196 }
197
198 #[test]
199 fn test_invalid_version_specifier() {
200 let err = NpmError::invalid_version_specifier("invalid", "not a valid semver");
201 assert!(err.to_string().contains("invalid"));
202 assert!(err.to_string().contains("not a valid semver"));
203 }
204
205 #[test]
206 fn test_conversion_to_deps_error() {
207 let npm_err = NpmError::PackageNotFound {
208 package: "test".into(),
209 };
210 let deps_err: deps_core::DepsError = npm_err.into();
211 assert!(deps_err.to_string().contains("not found"));
212 }
213}