1use std::ffi::{CStr, NulError};
49
50#[derive(Debug, Clone, Copy)]
52pub enum SzComponent {
53 Engine,
54 Config,
55 ConfigMgr,
56 Diagnostic,
57 Product,
58}
59use thiserror::Error;
60
61pub type SzResult<T> = Result<T, SzError>;
77
78#[derive(Error, Debug)]
87pub enum SzError {
88 #[error("Bad input: {message}")]
90 BadInput {
91 message: String,
92 #[source]
93 source: Option<Box<dyn std::error::Error + Send + Sync>>,
94 },
95
96 #[error("Configuration error: {message}")]
98 Configuration {
99 message: String,
100 #[source]
101 source: Option<Box<dyn std::error::Error + Send + Sync>>,
102 },
103
104 #[error("Database error: {message}")]
106 Database {
107 message: String,
108 #[source]
109 source: Option<Box<dyn std::error::Error + Send + Sync>>,
110 },
111
112 #[error("License error: {message}")]
114 License {
115 message: String,
116 #[source]
117 source: Option<Box<dyn std::error::Error + Send + Sync>>,
118 },
119
120 #[error("Not found: {message}")]
122 NotFound {
123 message: String,
124 #[source]
125 source: Option<Box<dyn std::error::Error + Send + Sync>>,
126 },
127
128 #[error("Retryable error: {message}")]
130 Retryable {
131 message: String,
132 #[source]
133 source: Option<Box<dyn std::error::Error + Send + Sync>>,
134 },
135
136 #[error("Unrecoverable error: {message}")]
138 Unrecoverable {
139 message: String,
140 #[source]
141 source: Option<Box<dyn std::error::Error + Send + Sync>>,
142 },
143
144 #[error("Unknown error: {message}")]
146 Unknown {
147 message: String,
148 #[source]
149 source: Option<Box<dyn std::error::Error + Send + Sync>>,
150 },
151
152 #[error("Not initialized: {message}")]
154 NotInitialized {
155 message: String,
156 #[source]
157 source: Option<Box<dyn std::error::Error + Send + Sync>>,
158 },
159
160 #[error("Database connection lost: {message}")]
162 DatabaseConnectionLost {
163 message: String,
164 #[source]
165 source: Option<Box<dyn std::error::Error + Send + Sync>>,
166 },
167
168 #[error("Database transient error: {message}")]
170 DatabaseTransient {
171 message: String,
172 #[source]
173 source: Option<Box<dyn std::error::Error + Send + Sync>>,
174 },
175
176 #[error("Replace conflict: {message}")]
178 ReplaceConflict {
179 message: String,
180 #[source]
181 source: Option<Box<dyn std::error::Error + Send + Sync>>,
182 },
183
184 #[error("Retry timeout exceeded: {message}")]
186 RetryTimeoutExceeded {
187 message: String,
188 #[source]
189 source: Option<Box<dyn std::error::Error + Send + Sync>>,
190 },
191
192 #[error("Unhandled error: {message}")]
194 Unhandled {
195 message: String,
196 #[source]
197 source: Option<Box<dyn std::error::Error + Send + Sync>>,
198 },
199
200 #[error("Unknown data source: {message}")]
202 UnknownDataSource {
203 message: String,
204 #[source]
205 source: Option<Box<dyn std::error::Error + Send + Sync>>,
206 },
207
208 #[error("FFI error: {message}")]
210 Ffi {
211 message: String,
212 #[source]
213 source: Option<Box<dyn std::error::Error + Send + Sync>>,
214 },
215
216 #[error("JSON error: {0}")]
218 Json(#[from] serde_json::Error),
219
220 #[error("String conversion error: {0}")]
222 StringConversion(#[from] NulError),
223}
224
225impl SzError {
226 pub fn bad_input<S: Into<String>>(message: S) -> Self {
228 Self::BadInput {
229 message: message.into(),
230 source: None,
231 }
232 }
233
234 pub fn configuration<S: Into<String>>(message: S) -> Self {
236 Self::Configuration {
237 message: message.into(),
238 source: None,
239 }
240 }
241
242 pub fn database<S: Into<String>>(message: S) -> Self {
244 Self::Database {
245 message: message.into(),
246 source: None,
247 }
248 }
249
250 pub fn license<S: Into<String>>(message: S) -> Self {
252 Self::License {
253 message: message.into(),
254 source: None,
255 }
256 }
257
258 pub fn not_found<S: Into<String>>(message: S) -> Self {
260 Self::NotFound {
261 message: message.into(),
262 source: None,
263 }
264 }
265
266 pub fn retryable<S: Into<String>>(message: S) -> Self {
268 Self::Retryable {
269 message: message.into(),
270 source: None,
271 }
272 }
273
274 pub fn unrecoverable<S: Into<String>>(message: S) -> Self {
276 Self::Unrecoverable {
277 message: message.into(),
278 source: None,
279 }
280 }
281
282 pub fn unknown<S: Into<String>>(message: S) -> Self {
284 Self::Unknown {
285 message: message.into(),
286 source: None,
287 }
288 }
289
290 pub fn ffi<S: Into<String>>(message: S) -> Self {
292 Self::Ffi {
293 message: message.into(),
294 source: None,
295 }
296 }
297
298 pub fn not_initialized<S: Into<String>>(message: S) -> Self {
300 Self::NotInitialized {
301 message: message.into(),
302 source: None,
303 }
304 }
305
306 pub fn database_connection_lost<S: Into<String>>(message: S) -> Self {
308 Self::DatabaseConnectionLost {
309 message: message.into(),
310 source: None,
311 }
312 }
313
314 pub fn database_transient<S: Into<String>>(message: S) -> Self {
316 Self::DatabaseTransient {
317 message: message.into(),
318 source: None,
319 }
320 }
321
322 pub fn replace_conflict<S: Into<String>>(message: S) -> Self {
324 Self::ReplaceConflict {
325 message: message.into(),
326 source: None,
327 }
328 }
329
330 pub fn retry_timeout_exceeded<S: Into<String>>(message: S) -> Self {
332 Self::RetryTimeoutExceeded {
333 message: message.into(),
334 source: None,
335 }
336 }
337
338 pub fn unhandled<S: Into<String>>(message: S) -> Self {
340 Self::Unhandled {
341 message: message.into(),
342 source: None,
343 }
344 }
345
346 pub fn unknown_data_source<S: Into<String>>(message: S) -> Self {
348 Self::UnknownDataSource {
349 message: message.into(),
350 source: None,
351 }
352 }
353
354 pub fn is_retryable(&self) -> bool {
356 matches!(self, SzError::Retryable { .. })
357 }
358
359 pub fn is_unrecoverable(&self) -> bool {
361 matches!(self, SzError::Unrecoverable { .. })
362 }
363
364 pub fn from_code_with_message(error_code: i64, component: SzComponent) -> Self {
366 let error_msg = Self::get_last_exception_message(component, error_code);
367
368 match error_code {
369 47..=63 => Self::not_initialized(error_msg), 0..=46 | 64..=100 => Self::bad_input(error_msg), 999 => Self::license(error_msg), 1000..=1020 => Self::database(error_msg), 2000..=2300 => Self::configuration(error_msg), 7200..=7299 => Self::configuration(error_msg), 7301..=7400 => Self::bad_input(error_msg), 8500..=8600 => Self::database(error_msg), 9000..=9099 | 9201..=9999 => Self::license(error_msg), 9100..=9200 => Self::configuration(error_msg), _ => Self::unknown(error_msg),
385 }
386 }
387
388 fn get_last_exception_message(component: SzComponent, error_code: i64) -> String {
390 use crate::ffi;
391 use libc::c_char;
392
393 const BUFFER_SIZE: usize = 4096;
394 let mut buffer = vec![0i8; BUFFER_SIZE];
395
396 let result = unsafe {
397 match component {
398 SzComponent::Engine => ffi::bindings::Sz_getLastException(
399 buffer.as_mut_ptr() as *mut c_char,
400 BUFFER_SIZE as i64,
401 ),
402 SzComponent::Config => ffi::bindings::SzConfig_getLastException(
403 buffer.as_mut_ptr() as *mut c_char,
404 BUFFER_SIZE as i64,
405 ),
406 SzComponent::ConfigMgr => ffi::bindings::SzConfigMgr_getLastException(
407 buffer.as_mut_ptr() as *mut c_char,
408 BUFFER_SIZE as i64,
409 ),
410 SzComponent::Diagnostic => ffi::bindings::SzDiagnostic_getLastException(
411 buffer.as_mut_ptr() as *mut c_char,
412 BUFFER_SIZE as i64,
413 ),
414 SzComponent::Product => ffi::bindings::SzProduct_getLastException(
415 buffer.as_mut_ptr() as *mut c_char,
416 BUFFER_SIZE as i64,
417 ),
418 }
419 };
420
421 if result > 0 {
422 unsafe {
424 match CStr::from_ptr(buffer.as_ptr()).to_str() {
425 Ok(message) if !message.is_empty() => message.to_string(),
426 _ => format!("Native error (code: {})", error_code),
427 }
428 }
429 } else {
430 format!("Native error (code: {})", error_code)
432 }
433 }
434
435 pub fn from_code(error_code: i64) -> Self {
437 Self::from_code_with_message(error_code, SzComponent::Engine)
439 }
440
441 pub fn from_source(source: Box<dyn std::error::Error + Send + Sync>) -> Self {
443 Self::Unknown {
444 message: source.to_string(),
445 source: Some(source),
446 }
447 }
448
449 pub fn with_message_and_source<S: Into<String>>(
451 message: S,
452 source: Box<dyn std::error::Error + Send + Sync>,
453 ) -> Self {
454 Self::Unknown {
455 message: message.into(),
456 source: Some(source),
457 }
458 }
459}
460
461#[allow(dead_code)]
467pub(crate) unsafe fn c_str_to_sz_error(c_str: *const i8) -> SzError {
468 if c_str.is_null() {
469 return SzError::unknown("Unknown error occurred");
470 }
471
472 match unsafe { CStr::from_ptr(c_str) }.to_str() {
473 Ok(error_msg) => SzError::unknown(error_msg),
474 Err(_) => SzError::ffi("Failed to convert C string to Rust string"),
475 }
476}
477
478#[cfg(test)]
479mod test_error_mapping {
480 use super::*;
481
482 #[test]
483 fn test_error_code_7220_maps_to_configuration() {
484 let error = SzError::from_code(7220);
485 match error {
486 SzError::Configuration { message, .. } => {
487 assert!(message.contains("7220") || !message.is_empty());
489 }
490 _ => panic!(
491 "Error code 7220 should map to Configuration, got: {:?}",
492 error
493 ),
494 }
495 }
496
497 #[test]
498 fn test_not_initialized_error_codes() {
499 for code in 47..=63 {
500 let error = SzError::from_code(code);
501 match error {
502 SzError::NotInitialized { .. } => {
503 }
505 _ => panic!(
506 "Error code {} should map to NotInitialized, got: {:?}",
507 code, error
508 ),
509 }
510 }
511 }
512
513 #[test]
514 fn test_license_error_code_999() {
515 let error = SzError::from_code(999);
516 match error {
517 SzError::License { .. } => {
518 }
520 _ => panic!("Error code 999 should map to License, got: {:?}", error),
521 }
522 }
523
524 #[test]
525 fn test_database_error_range() {
526 let error = SzError::from_code(1010);
527 match error {
528 SzError::Database { .. } => {
529 }
531 _ => panic!("Error code 1010 should map to Database, got: {:?}", error),
532 }
533 }
534
535 #[test]
536 fn test_unknown_error_default() {
537 let error = SzError::from_code(99999);
538 match error {
539 SzError::Unknown { .. } => {
540 }
542 _ => panic!("Error code 99999 should map to Unknown, got: {:?}", error),
543 }
544 }
545
546 #[test]
547 fn test_from_code_with_message() {
548 let error = SzError::from_code_with_message(7220, SzComponent::Config);
549 match error {
550 SzError::Configuration { message, .. } => {
551 assert!(!message.is_empty());
553 }
554 _ => panic!(
555 "Error code 7220 should map to Configuration, got: {:?}",
556 error
557 ),
558 }
559 }
560}