1#[cfg(feature = "std")]
29use thiserror::Error;
30
31#[cfg(feature = "std")]
32use std::io;
33
34#[derive(Debug)]
39#[cfg_attr(feature = "std", derive(Error))]
40pub enum DbError {
41 #[cfg_attr(feature = "std", error("Connection failed to {endpoint}: {reason}"))]
44 ConnectionFailed {
45 #[cfg(feature = "std")]
46 endpoint: String,
47 #[cfg(feature = "std")]
48 reason: String,
49 #[cfg(not(feature = "std"))]
50 _endpoint: (),
51 #[cfg(not(feature = "std"))]
52 _reason: (),
53 },
54
55 #[cfg_attr(feature = "std", error("Buffer full: {buffer_name} ({size} items)"))]
58 BufferFull {
59 size: u32,
60 #[cfg(feature = "std")]
61 buffer_name: String,
62 #[cfg(not(feature = "std"))]
63 _buffer_name: (),
64 },
65
66 #[cfg_attr(feature = "std", error("Consumer lagged by {lag_count} messages"))]
68 BufferLagged {
69 lag_count: u64,
70 #[cfg(feature = "std")]
71 buffer_name: String,
72 #[cfg(not(feature = "std"))]
73 _buffer_name: (),
74 },
75
76 #[cfg_attr(feature = "std", error("Buffer channel closed: {buffer_name}"))]
78 BufferClosed {
79 #[cfg(feature = "std")]
80 buffer_name: String,
81 #[cfg(not(feature = "std"))]
82 _buffer_name: (),
83 },
84
85 #[cfg_attr(feature = "std", error("Record type not found: {record_name}"))]
88 RecordNotFound {
89 #[cfg(feature = "std")]
90 record_name: String,
91 #[cfg(not(feature = "std"))]
92 _record_name: (),
93 },
94
95 #[cfg_attr(feature = "std", error("Invalid operation '{operation}': {reason}"))]
97 InvalidOperation {
98 #[cfg(feature = "std")]
99 operation: String,
100 #[cfg(feature = "std")]
101 reason: String,
102 #[cfg(not(feature = "std"))]
103 _operation: (),
104 #[cfg(not(feature = "std"))]
105 _reason: (),
106 },
107
108 #[cfg_attr(feature = "std", error("Permission denied: {operation}"))]
110 PermissionDenied {
111 #[cfg(feature = "std")]
112 operation: String,
113 #[cfg(not(feature = "std"))]
114 _operation: (),
115 },
116
117 #[cfg_attr(feature = "std", error("Missing configuration parameter: {parameter}"))]
120 MissingConfiguration {
121 #[cfg(feature = "std")]
122 parameter: String,
123 #[cfg(not(feature = "std"))]
124 _parameter: (),
125 },
126
127 #[cfg_attr(feature = "std", error("Runtime error: {message}"))]
130 RuntimeError {
131 #[cfg(feature = "std")]
132 message: String,
133 #[cfg(not(feature = "std"))]
134 _message: (),
135 },
136
137 #[cfg_attr(feature = "std", error("Resource unavailable: {resource_name}"))]
139 ResourceUnavailable {
140 resource_type: u8,
141 #[cfg(feature = "std")]
142 resource_name: String,
143 #[cfg(not(feature = "std"))]
144 _resource_name: (),
145 },
146
147 #[cfg_attr(
150 feature = "std",
151 error("Hardware error: component {component}, code 0x{error_code:04X}")
152 )]
153 HardwareError {
154 component: u8,
155 error_code: u16,
156 #[cfg(feature = "std")]
157 description: String,
158 #[cfg(not(feature = "std"))]
159 _description: (),
160 },
161
162 #[cfg_attr(feature = "std", error("Internal error (0x{code:04X}): {message}"))]
165 Internal {
166 code: u32,
167 #[cfg(feature = "std")]
168 message: String,
169 #[cfg(not(feature = "std"))]
170 _message: (),
171 },
172
173 #[cfg(feature = "std")]
176 #[error("Failed to attach database: {message}")]
177 AttachFailed { message: String },
178
179 #[cfg(feature = "std")]
181 #[error("Failed to detach database: {message}")]
182 DetachFailed { message: String },
183
184 #[cfg(feature = "std")]
186 #[error("Timeout while setting value")]
187 SetTimeout,
188
189 #[cfg(feature = "std")]
191 #[error("Timeout while getting value")]
192 GetTimeout,
193
194 #[cfg(feature = "std")]
196 #[error("Runtime thread has shut down")]
197 RuntimeShutdown,
198
199 #[cfg(feature = "std")]
202 #[error("I/O error: {source}")]
203 Io {
204 #[from]
205 source: io::Error,
206 },
207
208 #[cfg(feature = "std")]
210 #[error("I/O error: {context}: {source}")]
211 IoWithContext {
212 context: String,
213 #[source]
214 source: io::Error,
215 },
216
217 #[cfg(feature = "std")]
219 #[error("JSON error: {source}")]
220 Json {
221 #[from]
222 source: serde_json::Error,
223 },
224
225 #[cfg(feature = "std")]
227 #[error("JSON error: {context}: {source}")]
228 JsonWithContext {
229 context: String,
230 #[source]
231 source: serde_json::Error,
232 },
233}
234
235#[cfg(not(feature = "std"))]
237impl core::fmt::Display for DbError {
238 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
239 let (code, message) = match self {
240 DbError::ConnectionFailed { .. } => (0x1002, "Connection failed"),
241 DbError::BufferFull { .. } => (0x2002, "Buffer full"),
242 DbError::BufferLagged { .. } => (0xA001, "Buffer consumer lagged"),
243 DbError::BufferClosed { .. } => (0xA002, "Buffer channel closed"),
244 DbError::RecordNotFound { .. } => (0x7003, "Record not found"),
245 DbError::InvalidOperation { .. } => (0x7004, "Invalid operation"),
246 DbError::PermissionDenied { .. } => (0x7005, "Permission denied"),
247 DbError::MissingConfiguration { .. } => (0x4002, "Missing configuration"),
248 DbError::RuntimeError { .. } => (0x7002, "Runtime error"),
249 DbError::ResourceUnavailable { .. } => (0x5002, "Resource unavailable"),
250 DbError::HardwareError { .. } => (0x6001, "Hardware error"),
251 DbError::Internal { .. } => (0x7001, "Internal error"),
252 };
253 write!(f, "Error 0x{:04X}: {}", code, message)
254 }
255}
256
257impl DbError {
259 pub const RESOURCE_TYPE_MEMORY: u8 = 0;
261 pub const RESOURCE_TYPE_FILE_HANDLE: u8 = 1;
262 pub const RESOURCE_TYPE_SOCKET: u8 = 2;
263 pub const RESOURCE_TYPE_BUFFER: u8 = 3;
264 pub const RESOURCE_TYPE_THREAD: u8 = 4;
265 pub const RESOURCE_TYPE_MUTEX: u8 = 5;
266 pub const RESOURCE_TYPE_SEMAPHORE: u8 = 6;
267 pub const RESOURCE_TYPE_CHANNEL: u8 = 7;
268 pub const RESOURCE_TYPE_WOULD_BLOCK: u8 = 255;
269
270 pub fn hardware_error(component: u8, error_code: u16) -> Self {
272 DbError::HardwareError {
273 component,
274 error_code,
275 #[cfg(feature = "std")]
276 description: String::new(),
277 #[cfg(not(feature = "std"))]
278 _description: (),
279 }
280 }
281
282 pub fn internal(code: u32) -> Self {
284 DbError::Internal {
285 code,
286 #[cfg(feature = "std")]
287 message: String::new(),
288 #[cfg(not(feature = "std"))]
289 _message: (),
290 }
291 }
292
293 pub fn is_network_error(&self) -> bool {
295 matches!(self, DbError::ConnectionFailed { .. })
296 }
297
298 pub fn is_capacity_error(&self) -> bool {
300 matches!(self, DbError::BufferFull { .. })
301 }
302
303 pub fn is_hardware_error(&self) -> bool {
305 matches!(self, DbError::HardwareError { .. })
306 }
307
308 pub const fn error_code(&self) -> u32 {
310 match self {
311 DbError::ConnectionFailed { .. } => 0x1002,
313
314 DbError::BufferFull { .. } => 0x2002,
316
317 DbError::MissingConfiguration { .. } => 0x4002,
319
320 DbError::ResourceUnavailable { .. } => 0x5002,
322
323 DbError::HardwareError { .. } => 0x6001,
325
326 DbError::Internal { .. } => 0x7001,
328 DbError::RuntimeError { .. } => 0x7002,
329 DbError::RecordNotFound { .. } => 0x7003,
330 DbError::InvalidOperation { .. } => 0x7004,
331 DbError::PermissionDenied { .. } => 0x7005,
332
333 #[cfg(feature = "std")]
335 DbError::Io { .. } => 0x8001,
336 #[cfg(feature = "std")]
337 DbError::IoWithContext { .. } => 0x8002,
338
339 #[cfg(feature = "std")]
341 DbError::Json { .. } => 0x9001,
342 #[cfg(feature = "std")]
343 DbError::JsonWithContext { .. } => 0x9002,
344
345 DbError::BufferLagged { .. } => 0xA001,
347 DbError::BufferClosed { .. } => 0xA002,
348
349 #[cfg(feature = "std")]
351 DbError::AttachFailed { .. } => 0xB001,
352 #[cfg(feature = "std")]
353 DbError::DetachFailed { .. } => 0xB002,
354 #[cfg(feature = "std")]
355 DbError::SetTimeout => 0xB003,
356 #[cfg(feature = "std")]
357 DbError::GetTimeout => 0xB004,
358 #[cfg(feature = "std")]
359 DbError::RuntimeShutdown => 0xB005,
360 }
361 }
362
363 pub const fn error_category(&self) -> u32 {
365 self.error_code() & 0xF000
366 }
367
368 #[cfg(feature = "std")]
370 fn prepend_context<S: Into<String>>(existing: &mut String, new_context: S) {
371 let new_context = new_context.into();
372 existing.insert_str(0, ": ");
373 existing.insert_str(0, &new_context);
374 }
375
376 #[cfg(feature = "std")]
378 pub fn with_context<S: Into<String>>(self, context: S) -> Self {
379 match self {
380 DbError::ConnectionFailed {
381 mut reason,
382 endpoint,
383 } => {
384 Self::prepend_context(&mut reason, context);
385 DbError::ConnectionFailed { endpoint, reason }
386 }
387 DbError::BufferFull {
388 size,
389 mut buffer_name,
390 } => {
391 Self::prepend_context(&mut buffer_name, context);
392 DbError::BufferFull { size, buffer_name }
393 }
394 DbError::BufferLagged {
395 lag_count,
396 mut buffer_name,
397 } => {
398 Self::prepend_context(&mut buffer_name, context);
399 DbError::BufferLagged {
400 lag_count,
401 buffer_name,
402 }
403 }
404 DbError::BufferClosed { mut buffer_name } => {
405 Self::prepend_context(&mut buffer_name, context);
406 DbError::BufferClosed { buffer_name }
407 }
408 DbError::RecordNotFound { mut record_name } => {
409 Self::prepend_context(&mut record_name, context);
410 DbError::RecordNotFound { record_name }
411 }
412 DbError::InvalidOperation {
413 operation,
414 mut reason,
415 } => {
416 Self::prepend_context(&mut reason, context);
417 DbError::InvalidOperation { operation, reason }
418 }
419 DbError::PermissionDenied { mut operation } => {
420 Self::prepend_context(&mut operation, context);
421 DbError::PermissionDenied { operation }
422 }
423 DbError::MissingConfiguration { mut parameter } => {
424 Self::prepend_context(&mut parameter, context);
425 DbError::MissingConfiguration { parameter }
426 }
427 DbError::RuntimeError { mut message } => {
428 Self::prepend_context(&mut message, context);
429 DbError::RuntimeError { message }
430 }
431 DbError::ResourceUnavailable {
432 resource_type,
433 mut resource_name,
434 } => {
435 Self::prepend_context(&mut resource_name, context);
436 DbError::ResourceUnavailable {
437 resource_type,
438 resource_name,
439 }
440 }
441 DbError::HardwareError {
442 component,
443 error_code,
444 mut description,
445 } => {
446 Self::prepend_context(&mut description, context);
447 DbError::HardwareError {
448 component,
449 error_code,
450 description,
451 }
452 }
453 DbError::Internal { code, mut message } => {
454 Self::prepend_context(&mut message, context);
455 DbError::Internal { code, message }
456 }
457 #[cfg(feature = "std")]
459 DbError::AttachFailed { mut message } => {
460 Self::prepend_context(&mut message, context);
461 DbError::AttachFailed { message }
462 }
463 #[cfg(feature = "std")]
464 DbError::DetachFailed { mut message } => {
465 Self::prepend_context(&mut message, context);
466 DbError::DetachFailed { message }
467 }
468 #[cfg(feature = "std")]
470 DbError::SetTimeout => DbError::SetTimeout,
471 #[cfg(feature = "std")]
472 DbError::GetTimeout => DbError::GetTimeout,
473 #[cfg(feature = "std")]
474 DbError::RuntimeShutdown => DbError::RuntimeShutdown,
475 #[cfg(feature = "std")]
477 DbError::Io { source } => DbError::IoWithContext {
478 context: context.into(),
479 source,
480 },
481 #[cfg(feature = "std")]
482 DbError::Json { source } => DbError::JsonWithContext {
483 context: context.into(),
484 source,
485 },
486 #[cfg(feature = "std")]
488 DbError::IoWithContext {
489 context: mut ctx,
490 source,
491 } => {
492 Self::prepend_context(&mut ctx, context);
493 DbError::IoWithContext {
494 context: ctx,
495 source,
496 }
497 }
498 #[cfg(feature = "std")]
499 DbError::JsonWithContext {
500 context: mut ctx,
501 source,
502 } => {
503 Self::prepend_context(&mut ctx, context);
504 DbError::JsonWithContext {
505 context: ctx,
506 source,
507 }
508 }
509 }
510 }
511
512 #[cfg(feature = "std")]
514 pub fn into_anyhow(self) -> anyhow::Error {
515 self.into()
516 }
517}
518
519pub type DbResult<T> = Result<T, DbError>;
521
522impl From<aimdb_executor::ExecutorError> for DbError {
531 fn from(err: aimdb_executor::ExecutorError) -> Self {
532 use aimdb_executor::ExecutorError;
533
534 match err {
535 ExecutorError::SpawnFailed { message } => {
536 #[cfg(feature = "std")]
537 {
538 DbError::RuntimeError { message }
539 }
540 #[cfg(not(feature = "std"))]
541 {
542 let _ = message; DbError::RuntimeError { _message: () }
544 }
545 }
546 ExecutorError::RuntimeUnavailable { message } => {
547 #[cfg(feature = "std")]
548 {
549 DbError::RuntimeError { message }
550 }
551 #[cfg(not(feature = "std"))]
552 {
553 let _ = message; DbError::RuntimeError { _message: () }
555 }
556 }
557 ExecutorError::TaskJoinFailed { message } => {
558 #[cfg(feature = "std")]
559 {
560 DbError::RuntimeError { message }
561 }
562 #[cfg(not(feature = "std"))]
563 {
564 let _ = message; DbError::RuntimeError { _message: () }
566 }
567 }
568 }
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575
576 #[test]
577 fn test_error_size_constraint() {
578 let size = core::mem::size_of::<DbError>();
579 assert!(
580 size <= 64,
581 "DbError size ({} bytes) exceeds 64-byte embedded limit",
582 size
583 );
584 }
585
586 #[test]
587 fn test_error_codes() {
588 let connection_error = DbError::ConnectionFailed {
589 #[cfg(feature = "std")]
590 endpoint: "localhost".to_string(),
591 #[cfg(feature = "std")]
592 reason: "timeout".to_string(),
593 #[cfg(not(feature = "std"))]
594 _endpoint: (),
595 #[cfg(not(feature = "std"))]
596 _reason: (),
597 };
598 assert_eq!(connection_error.error_code(), 0x1002);
599 assert_eq!(connection_error.error_category(), 0x1000);
600
601 let buffer_error = DbError::BufferFull {
602 size: 1024,
603 #[cfg(feature = "std")]
604 buffer_name: String::new(),
605 #[cfg(not(feature = "std"))]
606 _buffer_name: (),
607 };
608 assert_eq!(buffer_error.error_code(), 0x2002);
609 }
610
611 #[test]
612 fn test_helper_methods() {
613 let connection_error = DbError::ConnectionFailed {
614 #[cfg(feature = "std")]
615 endpoint: "localhost".to_string(),
616 #[cfg(feature = "std")]
617 reason: "timeout".to_string(),
618 #[cfg(not(feature = "std"))]
619 _endpoint: (),
620 #[cfg(not(feature = "std"))]
621 _reason: (),
622 };
623
624 assert!(connection_error.is_network_error());
625 assert!(!connection_error.is_capacity_error());
626 assert!(!connection_error.is_hardware_error());
627
628 let hardware_error = DbError::hardware_error(2, 404);
629 assert!(hardware_error.is_hardware_error());
630
631 let internal_error = DbError::internal(500);
632 assert!(matches!(
633 internal_error,
634 DbError::Internal { code: 500, .. }
635 ));
636 }
637
638 #[cfg(feature = "std")]
639 #[test]
640 fn test_error_context() {
641 let error = DbError::ConnectionFailed {
642 endpoint: "localhost:5432".to_string(),
643 reason: "timeout".to_string(),
644 }
645 .with_context("Database connection")
646 .with_context("Application startup");
647
648 if let DbError::ConnectionFailed { reason, .. } = error {
649 assert_eq!(reason, "Application startup: Database connection: timeout");
650 } else {
651 panic!("Expected ConnectionFailed");
652 }
653 }
654
655 #[cfg(feature = "std")]
656 #[test]
657 fn test_io_json_conversions() {
658 let io_error = std::io::Error::other("File not found");
659 let db_error: DbError = io_error.into();
660 assert!(matches!(db_error, DbError::Io { .. }));
661
662 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
663 let db_error: DbError = json_error.into();
664 assert!(matches!(db_error, DbError::Json { .. }));
665 }
666}