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("Record key not found: {key}"))]
97 RecordKeyNotFound {
98 #[cfg(feature = "std")]
99 key: String,
100 #[cfg(not(feature = "std"))]
101 _key: (),
102 },
103
104 #[cfg_attr(feature = "std", error("Invalid record ID: {id}"))]
106 InvalidRecordId { id: u32 },
107
108 #[cfg_attr(
110 feature = "std",
111 error("Type mismatch: expected {expected_type}, record {record_id} has different type")
112 )]
113 TypeMismatch {
114 record_id: u32,
115 #[cfg(feature = "std")]
116 expected_type: String,
117 #[cfg(not(feature = "std"))]
118 _expected_type: (),
119 },
120
121 #[cfg_attr(
123 feature = "std",
124 error("Ambiguous type lookup: {type_name} has {count} records, use explicit key")
125 )]
126 AmbiguousType {
127 count: u32,
128 #[cfg(feature = "std")]
129 type_name: String,
130 #[cfg(not(feature = "std"))]
131 _type_name: (),
132 },
133
134 #[cfg_attr(feature = "std", error("Duplicate record key: {key}"))]
136 DuplicateRecordKey {
137 #[cfg(feature = "std")]
138 key: String,
139 #[cfg(not(feature = "std"))]
140 _key: (),
141 },
142
143 #[cfg_attr(feature = "std", error("Invalid operation '{operation}': {reason}"))]
145 InvalidOperation {
146 #[cfg(feature = "std")]
147 operation: String,
148 #[cfg(feature = "std")]
149 reason: String,
150 #[cfg(not(feature = "std"))]
151 _operation: (),
152 #[cfg(not(feature = "std"))]
153 _reason: (),
154 },
155
156 #[cfg_attr(feature = "std", error("Permission denied: {operation}"))]
158 PermissionDenied {
159 #[cfg(feature = "std")]
160 operation: String,
161 #[cfg(not(feature = "std"))]
162 _operation: (),
163 },
164
165 #[cfg_attr(feature = "std", error("Missing configuration parameter: {parameter}"))]
168 MissingConfiguration {
169 #[cfg(feature = "std")]
170 parameter: String,
171 #[cfg(not(feature = "std"))]
172 _parameter: (),
173 },
174
175 #[cfg_attr(feature = "std", error("Runtime error: {message}"))]
178 RuntimeError {
179 #[cfg(feature = "std")]
180 message: String,
181 #[cfg(not(feature = "std"))]
182 _message: (),
183 },
184
185 #[cfg_attr(feature = "std", error("Resource unavailable: {resource_name}"))]
187 ResourceUnavailable {
188 resource_type: u8,
189 #[cfg(feature = "std")]
190 resource_name: String,
191 #[cfg(not(feature = "std"))]
192 _resource_name: (),
193 },
194
195 #[cfg_attr(
198 feature = "std",
199 error("Hardware error: component {component}, code 0x{error_code:04X}")
200 )]
201 HardwareError {
202 component: u8,
203 error_code: u16,
204 #[cfg(feature = "std")]
205 description: String,
206 #[cfg(not(feature = "std"))]
207 _description: (),
208 },
209
210 #[cfg_attr(feature = "std", error("Internal error (0x{code:04X}): {message}"))]
213 Internal {
214 code: u32,
215 #[cfg(feature = "std")]
216 message: String,
217 #[cfg(not(feature = "std"))]
218 _message: (),
219 },
220
221 #[cfg(feature = "std")]
224 #[error("Failed to attach database: {message}")]
225 AttachFailed { message: String },
226
227 #[cfg(feature = "std")]
229 #[error("Failed to detach database: {message}")]
230 DetachFailed { message: String },
231
232 #[cfg(feature = "std")]
234 #[error("Timeout while setting value")]
235 SetTimeout,
236
237 #[cfg(feature = "std")]
239 #[error("Timeout while getting value")]
240 GetTimeout,
241
242 #[cfg(feature = "std")]
244 #[error("Runtime thread has shut down")]
245 RuntimeShutdown,
246
247 #[cfg(feature = "std")]
250 #[error("I/O error: {source}")]
251 Io {
252 #[from]
253 source: io::Error,
254 },
255
256 #[cfg(feature = "std")]
258 #[error("I/O error: {context}: {source}")]
259 IoWithContext {
260 context: String,
261 #[source]
262 source: io::Error,
263 },
264
265 #[cfg(feature = "std")]
267 #[error("JSON error: {source}")]
268 Json {
269 #[from]
270 source: serde_json::Error,
271 },
272
273 #[cfg(feature = "std")]
275 #[error("JSON error: {context}: {source}")]
276 JsonWithContext {
277 context: String,
278 #[source]
279 source: serde_json::Error,
280 },
281}
282
283#[cfg(not(feature = "std"))]
285impl core::fmt::Display for DbError {
286 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
287 let (code, message) = match self {
288 DbError::ConnectionFailed { .. } => (0x1002, "Connection failed"),
289 DbError::BufferFull { .. } => (0x2002, "Buffer full"),
290 DbError::BufferLagged { .. } => (0xA001, "Buffer consumer lagged"),
291 DbError::BufferClosed { .. } => (0xA002, "Buffer channel closed"),
292 DbError::RecordNotFound { .. } => (0x7003, "Record not found"),
293 DbError::RecordKeyNotFound { .. } => (0x7006, "Record key not found"),
294 DbError::InvalidRecordId { .. } => (0x7007, "Invalid record ID"),
295 DbError::TypeMismatch { .. } => (0x7008, "Type mismatch"),
296 DbError::AmbiguousType { .. } => (0x7009, "Ambiguous type lookup"),
297 DbError::DuplicateRecordKey { .. } => (0x700A, "Duplicate record key"),
298 DbError::InvalidOperation { .. } => (0x7004, "Invalid operation"),
299 DbError::PermissionDenied { .. } => (0x7005, "Permission denied"),
300 DbError::MissingConfiguration { .. } => (0x4002, "Missing configuration"),
301 DbError::RuntimeError { .. } => (0x7002, "Runtime error"),
302 DbError::ResourceUnavailable { .. } => (0x5002, "Resource unavailable"),
303 DbError::HardwareError { .. } => (0x6001, "Hardware error"),
304 DbError::Internal { .. } => (0x7001, "Internal error"),
305 };
306 write!(f, "Error 0x{:04X}: {}", code, message)
307 }
308}
309
310impl DbError {
312 pub const RESOURCE_TYPE_MEMORY: u8 = 0;
314 pub const RESOURCE_TYPE_FILE_HANDLE: u8 = 1;
315 pub const RESOURCE_TYPE_SOCKET: u8 = 2;
316 pub const RESOURCE_TYPE_BUFFER: u8 = 3;
317 pub const RESOURCE_TYPE_THREAD: u8 = 4;
318 pub const RESOURCE_TYPE_MUTEX: u8 = 5;
319 pub const RESOURCE_TYPE_SEMAPHORE: u8 = 6;
320 pub const RESOURCE_TYPE_CHANNEL: u8 = 7;
321 pub const RESOURCE_TYPE_WOULD_BLOCK: u8 = 255;
322
323 pub fn hardware_error(component: u8, error_code: u16) -> Self {
325 DbError::HardwareError {
326 component,
327 error_code,
328 #[cfg(feature = "std")]
329 description: String::new(),
330 #[cfg(not(feature = "std"))]
331 _description: (),
332 }
333 }
334
335 pub fn internal(code: u32) -> Self {
337 DbError::Internal {
338 code,
339 #[cfg(feature = "std")]
340 message: String::new(),
341 #[cfg(not(feature = "std"))]
342 _message: (),
343 }
344 }
345
346 pub fn is_network_error(&self) -> bool {
348 matches!(self, DbError::ConnectionFailed { .. })
349 }
350
351 pub fn is_capacity_error(&self) -> bool {
353 matches!(self, DbError::BufferFull { .. })
354 }
355
356 pub fn is_hardware_error(&self) -> bool {
358 matches!(self, DbError::HardwareError { .. })
359 }
360
361 pub const fn error_code(&self) -> u32 {
363 match self {
364 DbError::ConnectionFailed { .. } => 0x1002,
366
367 DbError::BufferFull { .. } => 0x2002,
369
370 DbError::MissingConfiguration { .. } => 0x4002,
372
373 DbError::ResourceUnavailable { .. } => 0x5002,
375
376 DbError::HardwareError { .. } => 0x6001,
378
379 DbError::Internal { .. } => 0x7001,
381 DbError::RuntimeError { .. } => 0x7002,
382 DbError::RecordNotFound { .. } => 0x7003,
383 DbError::InvalidOperation { .. } => 0x7004,
384 DbError::PermissionDenied { .. } => 0x7005,
385 DbError::RecordKeyNotFound { .. } => 0x7006,
386 DbError::InvalidRecordId { .. } => 0x7007,
387 DbError::TypeMismatch { .. } => 0x7008,
388 DbError::AmbiguousType { .. } => 0x7009,
389 DbError::DuplicateRecordKey { .. } => 0x700A,
390
391 #[cfg(feature = "std")]
393 DbError::Io { .. } => 0x8001,
394 #[cfg(feature = "std")]
395 DbError::IoWithContext { .. } => 0x8002,
396
397 #[cfg(feature = "std")]
399 DbError::Json { .. } => 0x9001,
400 #[cfg(feature = "std")]
401 DbError::JsonWithContext { .. } => 0x9002,
402
403 DbError::BufferLagged { .. } => 0xA001,
405 DbError::BufferClosed { .. } => 0xA002,
406
407 #[cfg(feature = "std")]
409 DbError::AttachFailed { .. } => 0xB001,
410 #[cfg(feature = "std")]
411 DbError::DetachFailed { .. } => 0xB002,
412 #[cfg(feature = "std")]
413 DbError::SetTimeout => 0xB003,
414 #[cfg(feature = "std")]
415 DbError::GetTimeout => 0xB004,
416 #[cfg(feature = "std")]
417 DbError::RuntimeShutdown => 0xB005,
418 }
419 }
420
421 pub const fn error_category(&self) -> u32 {
423 self.error_code() & 0xF000
424 }
425
426 #[cfg(feature = "std")]
428 fn prepend_context<S: Into<String>>(existing: &mut String, new_context: S) {
429 let new_context = new_context.into();
430 existing.insert_str(0, ": ");
431 existing.insert_str(0, &new_context);
432 }
433
434 #[cfg(feature = "std")]
436 pub fn with_context<S: Into<String>>(self, context: S) -> Self {
437 match self {
438 DbError::ConnectionFailed {
439 mut reason,
440 endpoint,
441 } => {
442 Self::prepend_context(&mut reason, context);
443 DbError::ConnectionFailed { endpoint, reason }
444 }
445 DbError::BufferFull {
446 size,
447 mut buffer_name,
448 } => {
449 Self::prepend_context(&mut buffer_name, context);
450 DbError::BufferFull { size, buffer_name }
451 }
452 DbError::BufferLagged {
453 lag_count,
454 mut buffer_name,
455 } => {
456 Self::prepend_context(&mut buffer_name, context);
457 DbError::BufferLagged {
458 lag_count,
459 buffer_name,
460 }
461 }
462 DbError::BufferClosed { mut buffer_name } => {
463 Self::prepend_context(&mut buffer_name, context);
464 DbError::BufferClosed { buffer_name }
465 }
466 DbError::RecordNotFound { mut record_name } => {
467 Self::prepend_context(&mut record_name, context);
468 DbError::RecordNotFound { record_name }
469 }
470 DbError::RecordKeyNotFound { mut key } => {
471 Self::prepend_context(&mut key, context);
472 DbError::RecordKeyNotFound { key }
473 }
474 DbError::InvalidRecordId { id } => {
475 DbError::InvalidRecordId { id }
477 }
478 DbError::TypeMismatch {
479 record_id,
480 mut expected_type,
481 } => {
482 Self::prepend_context(&mut expected_type, context);
483 DbError::TypeMismatch {
484 record_id,
485 expected_type,
486 }
487 }
488 DbError::AmbiguousType {
489 count,
490 mut type_name,
491 } => {
492 Self::prepend_context(&mut type_name, context);
493 DbError::AmbiguousType { count, type_name }
494 }
495 DbError::DuplicateRecordKey { mut key } => {
496 Self::prepend_context(&mut key, context);
497 DbError::DuplicateRecordKey { key }
498 }
499 DbError::InvalidOperation {
500 operation,
501 mut reason,
502 } => {
503 Self::prepend_context(&mut reason, context);
504 DbError::InvalidOperation { operation, reason }
505 }
506 DbError::PermissionDenied { mut operation } => {
507 Self::prepend_context(&mut operation, context);
508 DbError::PermissionDenied { operation }
509 }
510 DbError::MissingConfiguration { mut parameter } => {
511 Self::prepend_context(&mut parameter, context);
512 DbError::MissingConfiguration { parameter }
513 }
514 DbError::RuntimeError { mut message } => {
515 Self::prepend_context(&mut message, context);
516 DbError::RuntimeError { message }
517 }
518 DbError::ResourceUnavailable {
519 resource_type,
520 mut resource_name,
521 } => {
522 Self::prepend_context(&mut resource_name, context);
523 DbError::ResourceUnavailable {
524 resource_type,
525 resource_name,
526 }
527 }
528 DbError::HardwareError {
529 component,
530 error_code,
531 mut description,
532 } => {
533 Self::prepend_context(&mut description, context);
534 DbError::HardwareError {
535 component,
536 error_code,
537 description,
538 }
539 }
540 DbError::Internal { code, mut message } => {
541 Self::prepend_context(&mut message, context);
542 DbError::Internal { code, message }
543 }
544 #[cfg(feature = "std")]
546 DbError::AttachFailed { mut message } => {
547 Self::prepend_context(&mut message, context);
548 DbError::AttachFailed { message }
549 }
550 #[cfg(feature = "std")]
551 DbError::DetachFailed { mut message } => {
552 Self::prepend_context(&mut message, context);
553 DbError::DetachFailed { message }
554 }
555 #[cfg(feature = "std")]
557 DbError::SetTimeout => DbError::SetTimeout,
558 #[cfg(feature = "std")]
559 DbError::GetTimeout => DbError::GetTimeout,
560 #[cfg(feature = "std")]
561 DbError::RuntimeShutdown => DbError::RuntimeShutdown,
562 #[cfg(feature = "std")]
564 DbError::Io { source } => DbError::IoWithContext {
565 context: context.into(),
566 source,
567 },
568 #[cfg(feature = "std")]
569 DbError::Json { source } => DbError::JsonWithContext {
570 context: context.into(),
571 source,
572 },
573 #[cfg(feature = "std")]
575 DbError::IoWithContext {
576 context: mut ctx,
577 source,
578 } => {
579 Self::prepend_context(&mut ctx, context);
580 DbError::IoWithContext {
581 context: ctx,
582 source,
583 }
584 }
585 #[cfg(feature = "std")]
586 DbError::JsonWithContext {
587 context: mut ctx,
588 source,
589 } => {
590 Self::prepend_context(&mut ctx, context);
591 DbError::JsonWithContext {
592 context: ctx,
593 source,
594 }
595 }
596 }
597 }
598
599 #[cfg(feature = "std")]
601 pub fn into_anyhow(self) -> anyhow::Error {
602 self.into()
603 }
604}
605
606pub type DbResult<T> = Result<T, DbError>;
608
609impl From<aimdb_executor::ExecutorError> for DbError {
618 fn from(err: aimdb_executor::ExecutorError) -> Self {
619 use aimdb_executor::ExecutorError;
620
621 match err {
622 ExecutorError::SpawnFailed { message } => {
623 #[cfg(feature = "std")]
624 {
625 DbError::RuntimeError { message }
626 }
627 #[cfg(not(feature = "std"))]
628 {
629 let _ = message; DbError::RuntimeError { _message: () }
631 }
632 }
633 ExecutorError::RuntimeUnavailable { message } => {
634 #[cfg(feature = "std")]
635 {
636 DbError::RuntimeError { message }
637 }
638 #[cfg(not(feature = "std"))]
639 {
640 let _ = message; DbError::RuntimeError { _message: () }
642 }
643 }
644 ExecutorError::TaskJoinFailed { message } => {
645 #[cfg(feature = "std")]
646 {
647 DbError::RuntimeError { message }
648 }
649 #[cfg(not(feature = "std"))]
650 {
651 let _ = message; DbError::RuntimeError { _message: () }
653 }
654 }
655 }
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662
663 #[test]
664 fn test_error_size_constraint() {
665 let size = core::mem::size_of::<DbError>();
666 assert!(
667 size <= 64,
668 "DbError size ({} bytes) exceeds 64-byte embedded limit",
669 size
670 );
671 }
672
673 #[test]
674 fn test_error_codes() {
675 let connection_error = DbError::ConnectionFailed {
676 #[cfg(feature = "std")]
677 endpoint: "localhost".to_string(),
678 #[cfg(feature = "std")]
679 reason: "timeout".to_string(),
680 #[cfg(not(feature = "std"))]
681 _endpoint: (),
682 #[cfg(not(feature = "std"))]
683 _reason: (),
684 };
685 assert_eq!(connection_error.error_code(), 0x1002);
686 assert_eq!(connection_error.error_category(), 0x1000);
687
688 let buffer_error = DbError::BufferFull {
689 size: 1024,
690 #[cfg(feature = "std")]
691 buffer_name: String::new(),
692 #[cfg(not(feature = "std"))]
693 _buffer_name: (),
694 };
695 assert_eq!(buffer_error.error_code(), 0x2002);
696 }
697
698 #[test]
699 fn test_helper_methods() {
700 let connection_error = DbError::ConnectionFailed {
701 #[cfg(feature = "std")]
702 endpoint: "localhost".to_string(),
703 #[cfg(feature = "std")]
704 reason: "timeout".to_string(),
705 #[cfg(not(feature = "std"))]
706 _endpoint: (),
707 #[cfg(not(feature = "std"))]
708 _reason: (),
709 };
710
711 assert!(connection_error.is_network_error());
712 assert!(!connection_error.is_capacity_error());
713 assert!(!connection_error.is_hardware_error());
714
715 let hardware_error = DbError::hardware_error(2, 404);
716 assert!(hardware_error.is_hardware_error());
717
718 let internal_error = DbError::internal(500);
719 assert!(matches!(
720 internal_error,
721 DbError::Internal { code: 500, .. }
722 ));
723 }
724
725 #[cfg(feature = "std")]
726 #[test]
727 fn test_error_context() {
728 let error = DbError::ConnectionFailed {
729 endpoint: "localhost:5432".to_string(),
730 reason: "timeout".to_string(),
731 }
732 .with_context("Database connection")
733 .with_context("Application startup");
734
735 if let DbError::ConnectionFailed { reason, .. } = error {
736 assert_eq!(reason, "Application startup: Database connection: timeout");
737 } else {
738 panic!("Expected ConnectionFailed");
739 }
740 }
741
742 #[cfg(feature = "std")]
743 #[test]
744 fn test_io_json_conversions() {
745 let io_error = std::io::Error::other("File not found");
746 let db_error: DbError = io_error.into();
747 assert!(matches!(db_error, DbError::Io { .. }));
748
749 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
750 let db_error: DbError = json_error.into();
751 assert!(matches!(db_error, DbError::Json { .. }));
752 }
753}