corteq_onepassword/
error.rs1use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Error, Debug)]
16pub enum Error {
17 #[error("missing authentication token: set OP_SERVICE_ACCOUNT_TOKEN or use from_token()")]
20 MissingAuthToken,
21
22 #[error("invalid authentication token format")]
24 InvalidToken,
25
26 #[error("authentication failed: {message}")]
29 AuthenticationFailed {
30 message: String,
32 },
33
34 #[error("session error: {message}")]
36 SessionError {
37 message: String,
39 },
40
41 #[error("invalid secret reference '{reference}': {reason}")]
45 InvalidReference {
46 reference: String,
48 reason: String,
50 },
51
52 #[error("secret not found: {reference}")]
54 SecretNotFound {
55 reference: String,
57 },
58
59 #[error("access denied to vault: {vault}")]
62 AccessDenied {
63 vault: String,
65 },
66
67 #[error("network error: {message}")]
69 NetworkError {
70 message: String,
72 },
73
74 #[error("SDK error: {message}")]
76 SdkError {
77 message: String,
79 },
80
81 #[error("failed to load native library: {message}")]
83 LibraryLoadError {
84 message: String,
86 },
87
88 #[error("JSON error: {message}")]
90 JsonError {
91 message: String,
93 },
94}
95
96impl Error {
97 pub fn is_retriable(&self) -> bool {
99 matches!(
100 self,
101 Error::NetworkError { .. } | Error::SessionError { .. }
102 )
103 }
104
105 pub fn is_auth_error(&self) -> bool {
107 matches!(
108 self,
109 Error::MissingAuthToken
110 | Error::InvalidToken
111 | Error::AuthenticationFailed { .. }
112 | Error::AccessDenied { .. }
113 )
114 }
115}
116
117impl From<serde_json::Error> for Error {
118 fn from(err: serde_json::Error) -> Self {
119 Error::JsonError {
120 message: err.to_string(),
121 }
122 }
123}
124
125impl From<libloading::Error> for Error {
126 fn from(err: libloading::Error) -> Self {
127 Error::LibraryLoadError {
128 message: err.to_string(),
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_error_is_send_sync() {
139 fn assert_send_sync<T: Send + Sync>() {}
140 assert_send_sync::<Error>();
141 }
142
143 #[test]
144 fn test_error_display_no_secrets() {
145 let error = Error::AuthenticationFailed {
146 message: "token expired".to_string(),
147 };
148 let display = error.to_string();
149 assert!(!display.contains("ops_"));
150 }
151
152 #[test]
157 fn test_is_retriable_network_error() {
158 let error = Error::NetworkError {
159 message: "connection reset".to_string(),
160 };
161 assert!(error.is_retriable());
162 }
163
164 #[test]
165 fn test_is_retriable_session_error() {
166 let error = Error::SessionError {
167 message: "session expired".to_string(),
168 };
169 assert!(error.is_retriable());
170 }
171
172 #[test]
173 fn test_is_retriable_non_retriable_errors() {
174 let non_retriable = vec![
176 Error::MissingAuthToken,
177 Error::InvalidToken,
178 Error::AuthenticationFailed {
179 message: "bad token".to_string(),
180 },
181 Error::InvalidReference {
182 reference: "op://x".to_string(),
183 reason: "too short".to_string(),
184 },
185 Error::SecretNotFound {
186 reference: "op://vault/item/field".to_string(),
187 },
188 Error::AccessDenied {
189 vault: "private".to_string(),
190 },
191 Error::SdkError {
192 message: "internal".to_string(),
193 },
194 Error::LibraryLoadError {
195 message: "not found".to_string(),
196 },
197 Error::JsonError {
198 message: "parse error".to_string(),
199 },
200 ];
201
202 for error in non_retriable {
203 assert!(!error.is_retriable(), "{error:?} should not be retriable");
204 }
205 }
206
207 #[test]
212 fn test_is_auth_error_missing_token() {
213 assert!(Error::MissingAuthToken.is_auth_error());
214 }
215
216 #[test]
217 fn test_is_auth_error_invalid_token() {
218 assert!(Error::InvalidToken.is_auth_error());
219 }
220
221 #[test]
222 fn test_is_auth_error_auth_failed() {
223 let error = Error::AuthenticationFailed {
224 message: "token expired".to_string(),
225 };
226 assert!(error.is_auth_error());
227 }
228
229 #[test]
230 fn test_is_auth_error_access_denied() {
231 let error = Error::AccessDenied {
232 vault: "private-vault".to_string(),
233 };
234 assert!(error.is_auth_error());
235 }
236
237 #[test]
238 fn test_is_auth_error_non_auth_errors() {
239 let non_auth = vec![
241 Error::SessionError {
242 message: "session expired".to_string(),
243 },
244 Error::InvalidReference {
245 reference: "op://x".to_string(),
246 reason: "too short".to_string(),
247 },
248 Error::SecretNotFound {
249 reference: "op://vault/item/field".to_string(),
250 },
251 Error::NetworkError {
252 message: "timeout".to_string(),
253 },
254 Error::SdkError {
255 message: "internal".to_string(),
256 },
257 Error::LibraryLoadError {
258 message: "not found".to_string(),
259 },
260 Error::JsonError {
261 message: "parse error".to_string(),
262 },
263 ];
264
265 for error in non_auth {
266 assert!(!error.is_auth_error(), "{error:?} should not be auth error");
267 }
268 }
269
270 #[test]
275 fn test_from_serde_json_error() {
276 let json_err = serde_json::from_str::<String>("not valid json").unwrap_err();
278 let error: Error = json_err.into();
279
280 assert!(matches!(error, Error::JsonError { .. }));
281 let display = error.to_string();
282 assert!(display.contains("JSON error"));
283 }
284
285 #[test]
286 fn test_from_libloading_error() {
287 let lib_err =
289 unsafe { libloading::Library::new("/nonexistent/path/to/lib.so") }.unwrap_err();
290 let error: Error = lib_err.into();
291
292 assert!(matches!(error, Error::LibraryLoadError { .. }));
293 let display = error.to_string();
294 assert!(display.contains("native library"));
295 }
296
297 #[test]
302 fn test_error_display_missing_auth_token() {
303 let error = Error::MissingAuthToken;
304 let display = error.to_string();
305 assert!(display.contains("missing authentication token"));
306 assert!(display.contains("OP_SERVICE_ACCOUNT_TOKEN"));
307 }
308
309 #[test]
310 fn test_error_display_invalid_token() {
311 let error = Error::InvalidToken;
312 let display = error.to_string();
313 assert!(display.contains("invalid authentication token format"));
314 }
315
316 #[test]
317 fn test_error_display_authentication_failed() {
318 let error = Error::AuthenticationFailed {
319 message: "token expired".to_string(),
320 };
321 let display = error.to_string();
322 assert!(display.contains("authentication failed"));
323 assert!(display.contains("token expired"));
324 }
325
326 #[test]
327 fn test_error_display_session_error() {
328 let error = Error::SessionError {
329 message: "connection lost".to_string(),
330 };
331 let display = error.to_string();
332 assert!(display.contains("session error"));
333 assert!(display.contains("connection lost"));
334 }
335
336 #[test]
337 fn test_error_display_invalid_reference() {
338 let error = Error::InvalidReference {
339 reference: "op://vault".to_string(),
340 reason: "missing item and field".to_string(),
341 };
342 let display = error.to_string();
343 assert!(display.contains("invalid secret reference"));
344 assert!(display.contains("op://vault"));
345 assert!(display.contains("missing item and field"));
346 }
347
348 #[test]
349 fn test_error_display_secret_not_found() {
350 let error = Error::SecretNotFound {
351 reference: "op://vault/item/field".to_string(),
352 };
353 let display = error.to_string();
354 assert!(display.contains("secret not found"));
355 assert!(display.contains("op://vault/item/field"));
356 }
357
358 #[test]
359 fn test_error_display_access_denied() {
360 let error = Error::AccessDenied {
361 vault: "private-vault".to_string(),
362 };
363 let display = error.to_string();
364 assert!(display.contains("access denied"));
365 assert!(display.contains("private-vault"));
366 }
367
368 #[test]
369 fn test_error_display_network_error() {
370 let error = Error::NetworkError {
371 message: "connection timed out".to_string(),
372 };
373 let display = error.to_string();
374 assert!(display.contains("network error"));
375 assert!(display.contains("connection timed out"));
376 }
377
378 #[test]
379 fn test_error_display_sdk_error() {
380 let error = Error::SdkError {
381 message: "internal SDK failure".to_string(),
382 };
383 let display = error.to_string();
384 assert!(display.contains("SDK error"));
385 assert!(display.contains("internal SDK failure"));
386 }
387
388 #[test]
389 fn test_error_display_library_load_error() {
390 let error = Error::LibraryLoadError {
391 message: "library not found".to_string(),
392 };
393 let display = error.to_string();
394 assert!(display.contains("native library"));
395 assert!(display.contains("library not found"));
396 }
397
398 #[test]
399 fn test_error_display_json_error() {
400 let error = Error::JsonError {
401 message: "unexpected token".to_string(),
402 };
403 let display = error.to_string();
404 assert!(display.contains("JSON error"));
405 assert!(display.contains("unexpected token"));
406 }
407
408 #[test]
413 fn test_error_debug_impl() {
414 let error = Error::SdkError {
415 message: "test".to_string(),
416 };
417 let debug_str = format!("{error:?}");
418 assert!(debug_str.contains("SdkError"));
419 }
420}