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("Buffer empty: no pending values"))]
87 BufferEmpty,
88
89 #[cfg_attr(feature = "std", error("Record type not found: {record_name}"))]
92 RecordNotFound {
93 #[cfg(feature = "std")]
94 record_name: String,
95 #[cfg(not(feature = "std"))]
96 _record_name: (),
97 },
98
99 #[cfg_attr(feature = "std", error("Record key not found: {key}"))]
101 RecordKeyNotFound {
102 #[cfg(feature = "std")]
103 key: String,
104 #[cfg(not(feature = "std"))]
105 _key: (),
106 },
107
108 #[cfg_attr(feature = "std", error("Invalid record ID: {id}"))]
110 InvalidRecordId { id: u32 },
111
112 #[cfg_attr(
114 feature = "std",
115 error("Type mismatch: expected {expected_type}, record {record_id} has different type")
116 )]
117 TypeMismatch {
118 record_id: u32,
119 #[cfg(feature = "std")]
120 expected_type: String,
121 #[cfg(not(feature = "std"))]
122 _expected_type: (),
123 },
124
125 #[cfg_attr(
127 feature = "std",
128 error("Ambiguous type lookup: {type_name} has {count} records, use explicit key")
129 )]
130 AmbiguousType {
131 count: u32,
132 #[cfg(feature = "std")]
133 type_name: String,
134 #[cfg(not(feature = "std"))]
135 _type_name: (),
136 },
137
138 #[cfg_attr(feature = "std", error("Duplicate record key: {key}"))]
140 DuplicateRecordKey {
141 #[cfg(feature = "std")]
142 key: String,
143 #[cfg(not(feature = "std"))]
144 _key: (),
145 },
146
147 #[cfg_attr(feature = "std", error("Invalid operation '{operation}': {reason}"))]
149 InvalidOperation {
150 #[cfg(feature = "std")]
151 operation: String,
152 #[cfg(feature = "std")]
153 reason: String,
154 #[cfg(not(feature = "std"))]
155 _operation: (),
156 #[cfg(not(feature = "std"))]
157 _reason: (),
158 },
159
160 #[cfg_attr(feature = "std", error("Permission denied: {operation}"))]
162 PermissionDenied {
163 #[cfg(feature = "std")]
164 operation: String,
165 #[cfg(not(feature = "std"))]
166 _operation: (),
167 },
168
169 #[cfg_attr(feature = "std", error("Missing configuration parameter: {parameter}"))]
172 MissingConfiguration {
173 #[cfg(feature = "std")]
174 parameter: String,
175 #[cfg(not(feature = "std"))]
176 _parameter: (),
177 },
178
179 #[cfg_attr(feature = "std", error("Runtime error: {message}"))]
182 RuntimeError {
183 #[cfg(feature = "std")]
184 message: String,
185 #[cfg(not(feature = "std"))]
186 _message: (),
187 },
188
189 #[cfg_attr(feature = "std", error("Resource unavailable: {resource_name}"))]
191 ResourceUnavailable {
192 resource_type: u8,
193 #[cfg(feature = "std")]
194 resource_name: String,
195 #[cfg(not(feature = "std"))]
196 _resource_name: (),
197 },
198
199 #[cfg_attr(
202 feature = "std",
203 error("Hardware error: component {component}, code 0x{error_code:04X}")
204 )]
205 HardwareError {
206 component: u8,
207 error_code: u16,
208 #[cfg(feature = "std")]
209 description: String,
210 #[cfg(not(feature = "std"))]
211 _description: (),
212 },
213
214 #[cfg_attr(feature = "std", error("Internal error (0x{code:04X}): {message}"))]
217 Internal {
218 code: u32,
219 #[cfg(feature = "std")]
220 message: String,
221 #[cfg(not(feature = "std"))]
222 _message: (),
223 },
224
225 #[cfg(feature = "std")]
228 #[error("Failed to attach database: {message}")]
229 AttachFailed { message: String },
230
231 #[cfg(feature = "std")]
233 #[error("Failed to detach database: {message}")]
234 DetachFailed { message: String },
235
236 #[cfg(feature = "std")]
238 #[error("Timeout while setting value")]
239 SetTimeout,
240
241 #[cfg(feature = "std")]
243 #[error("Timeout while getting value")]
244 GetTimeout,
245
246 #[cfg(feature = "std")]
248 #[error("Runtime thread has shut down")]
249 RuntimeShutdown,
250
251 #[cfg_attr(
254 feature = "std",
255 error("Cyclic dependency detected among records: {records:?}")
256 )]
257 CyclicDependency {
258 #[cfg(feature = "std")]
259 records: Vec<String>,
260 #[cfg(not(feature = "std"))]
261 _records: (),
262 },
263
264 #[cfg_attr(
266 feature = "std",
267 error("Transform on '{output_key}' references unregistered input '{input_key}'")
268 )]
269 TransformInputNotFound {
270 #[cfg(feature = "std")]
271 output_key: String,
272 #[cfg(feature = "std")]
273 input_key: String,
274 #[cfg(not(feature = "std"))]
275 _output_key: (),
276 #[cfg(not(feature = "std"))]
277 _input_key: (),
278 },
279
280 #[cfg(feature = "std")]
283 #[error("I/O error: {source}")]
284 Io {
285 #[from]
286 source: io::Error,
287 },
288
289 #[cfg(feature = "std")]
291 #[error("I/O error: {context}: {source}")]
292 IoWithContext {
293 context: String,
294 #[source]
295 source: io::Error,
296 },
297
298 #[cfg(feature = "std")]
300 #[error("JSON error: {source}")]
301 Json {
302 #[from]
303 source: serde_json::Error,
304 },
305
306 #[cfg(feature = "std")]
308 #[error("JSON error: {context}: {source}")]
309 JsonWithContext {
310 context: String,
311 #[source]
312 source: serde_json::Error,
313 },
314}
315
316#[cfg(not(feature = "std"))]
318impl core::fmt::Display for DbError {
319 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
320 let (code, message) = match self {
321 DbError::ConnectionFailed { .. } => (0x1002, "Connection failed"),
322 DbError::BufferFull { .. } => (0x2002, "Buffer full"),
323 DbError::BufferLagged { .. } => (0xA001, "Buffer consumer lagged"),
324 DbError::BufferClosed { .. } => (0xA002, "Buffer channel closed"),
325 DbError::BufferEmpty => (0xA003, "Buffer empty"),
326 DbError::RecordNotFound { .. } => (0x7003, "Record not found"),
327 DbError::RecordKeyNotFound { .. } => (0x7006, "Record key not found"),
328 DbError::InvalidRecordId { .. } => (0x7007, "Invalid record ID"),
329 DbError::TypeMismatch { .. } => (0x7008, "Type mismatch"),
330 DbError::AmbiguousType { .. } => (0x7009, "Ambiguous type lookup"),
331 DbError::DuplicateRecordKey { .. } => (0x700A, "Duplicate record key"),
332 DbError::InvalidOperation { .. } => (0x7004, "Invalid operation"),
333 DbError::PermissionDenied { .. } => (0x7005, "Permission denied"),
334 DbError::MissingConfiguration { .. } => (0x4002, "Missing configuration"),
335 DbError::RuntimeError { .. } => (0x7002, "Runtime error"),
336 DbError::ResourceUnavailable { .. } => (0x5002, "Resource unavailable"),
337 DbError::HardwareError { .. } => (0x6001, "Hardware error"),
338 DbError::Internal { .. } => (0x7001, "Internal error"),
339 DbError::CyclicDependency { .. } => (0xC001, "Cyclic dependency in transforms"),
340 DbError::TransformInputNotFound { .. } => (0xC002, "Transform input not found"),
341 };
342 write!(f, "Error 0x{:04X}: {}", code, message)
343 }
344}
345
346impl DbError {
348 pub const RESOURCE_TYPE_MEMORY: u8 = 0;
350 pub const RESOURCE_TYPE_FILE_HANDLE: u8 = 1;
351 pub const RESOURCE_TYPE_SOCKET: u8 = 2;
352 pub const RESOURCE_TYPE_BUFFER: u8 = 3;
353 pub const RESOURCE_TYPE_THREAD: u8 = 4;
354 pub const RESOURCE_TYPE_MUTEX: u8 = 5;
355 pub const RESOURCE_TYPE_SEMAPHORE: u8 = 6;
356 pub const RESOURCE_TYPE_CHANNEL: u8 = 7;
357 pub const RESOURCE_TYPE_WOULD_BLOCK: u8 = 255;
358
359 pub fn hardware_error(component: u8, error_code: u16) -> Self {
361 DbError::HardwareError {
362 component,
363 error_code,
364 #[cfg(feature = "std")]
365 description: String::new(),
366 #[cfg(not(feature = "std"))]
367 _description: (),
368 }
369 }
370
371 pub fn internal(code: u32) -> Self {
373 DbError::Internal {
374 code,
375 #[cfg(feature = "std")]
376 message: String::new(),
377 #[cfg(not(feature = "std"))]
378 _message: (),
379 }
380 }
381
382 pub fn is_network_error(&self) -> bool {
384 matches!(self, DbError::ConnectionFailed { .. })
385 }
386
387 pub fn is_capacity_error(&self) -> bool {
389 matches!(self, DbError::BufferFull { .. })
390 }
391
392 pub fn is_hardware_error(&self) -> bool {
394 matches!(self, DbError::HardwareError { .. })
395 }
396
397 pub const fn error_code(&self) -> u32 {
399 match self {
400 DbError::ConnectionFailed { .. } => 0x1002,
402
403 DbError::BufferFull { .. } => 0x2002,
405
406 DbError::MissingConfiguration { .. } => 0x4002,
408
409 DbError::ResourceUnavailable { .. } => 0x5002,
411
412 DbError::HardwareError { .. } => 0x6001,
414
415 DbError::Internal { .. } => 0x7001,
417 DbError::RuntimeError { .. } => 0x7002,
418 DbError::RecordNotFound { .. } => 0x7003,
419 DbError::InvalidOperation { .. } => 0x7004,
420 DbError::PermissionDenied { .. } => 0x7005,
421 DbError::RecordKeyNotFound { .. } => 0x7006,
422 DbError::InvalidRecordId { .. } => 0x7007,
423 DbError::TypeMismatch { .. } => 0x7008,
424 DbError::AmbiguousType { .. } => 0x7009,
425 DbError::DuplicateRecordKey { .. } => 0x700A,
426
427 DbError::CyclicDependency { .. } => 0xC001,
429 DbError::TransformInputNotFound { .. } => 0xC002,
430
431 #[cfg(feature = "std")]
433 DbError::Io { .. } => 0x8001,
434 #[cfg(feature = "std")]
435 DbError::IoWithContext { .. } => 0x8002,
436
437 #[cfg(feature = "std")]
439 DbError::Json { .. } => 0x9001,
440 #[cfg(feature = "std")]
441 DbError::JsonWithContext { .. } => 0x9002,
442
443 DbError::BufferLagged { .. } => 0xA001,
445 DbError::BufferClosed { .. } => 0xA002,
446 DbError::BufferEmpty => 0xA003,
447
448 #[cfg(feature = "std")]
450 DbError::AttachFailed { .. } => 0xB001,
451 #[cfg(feature = "std")]
452 DbError::DetachFailed { .. } => 0xB002,
453 #[cfg(feature = "std")]
454 DbError::SetTimeout => 0xB003,
455 #[cfg(feature = "std")]
456 DbError::GetTimeout => 0xB004,
457 #[cfg(feature = "std")]
458 DbError::RuntimeShutdown => 0xB005,
459 }
460 }
461
462 pub const fn error_category(&self) -> u32 {
464 self.error_code() & 0xF000
465 }
466
467 #[cfg(feature = "std")]
469 fn prepend_context<S: Into<String>>(existing: &mut String, new_context: S) {
470 let new_context = new_context.into();
471 existing.insert_str(0, ": ");
472 existing.insert_str(0, &new_context);
473 }
474
475 #[cfg(feature = "std")]
477 pub fn with_context<S: Into<String>>(self, context: S) -> Self {
478 match self {
479 DbError::ConnectionFailed {
480 mut reason,
481 endpoint,
482 } => {
483 Self::prepend_context(&mut reason, context);
484 DbError::ConnectionFailed { endpoint, reason }
485 }
486 DbError::BufferFull {
487 size,
488 mut buffer_name,
489 } => {
490 Self::prepend_context(&mut buffer_name, context);
491 DbError::BufferFull { size, buffer_name }
492 }
493 DbError::BufferLagged {
494 lag_count,
495 mut buffer_name,
496 } => {
497 Self::prepend_context(&mut buffer_name, context);
498 DbError::BufferLagged {
499 lag_count,
500 buffer_name,
501 }
502 }
503 DbError::BufferClosed { mut buffer_name } => {
504 Self::prepend_context(&mut buffer_name, context);
505 DbError::BufferClosed { buffer_name }
506 }
507 DbError::BufferEmpty => DbError::BufferEmpty,
508 DbError::RecordNotFound { mut record_name } => {
509 Self::prepend_context(&mut record_name, context);
510 DbError::RecordNotFound { record_name }
511 }
512 DbError::RecordKeyNotFound { mut key } => {
513 Self::prepend_context(&mut key, context);
514 DbError::RecordKeyNotFound { key }
515 }
516 DbError::InvalidRecordId { id } => {
517 DbError::InvalidRecordId { id }
519 }
520 DbError::TypeMismatch {
521 record_id,
522 mut expected_type,
523 } => {
524 Self::prepend_context(&mut expected_type, context);
525 DbError::TypeMismatch {
526 record_id,
527 expected_type,
528 }
529 }
530 DbError::AmbiguousType {
531 count,
532 mut type_name,
533 } => {
534 Self::prepend_context(&mut type_name, context);
535 DbError::AmbiguousType { count, type_name }
536 }
537 DbError::DuplicateRecordKey { mut key } => {
538 Self::prepend_context(&mut key, context);
539 DbError::DuplicateRecordKey { key }
540 }
541 DbError::InvalidOperation {
542 operation,
543 mut reason,
544 } => {
545 Self::prepend_context(&mut reason, context);
546 DbError::InvalidOperation { operation, reason }
547 }
548 DbError::PermissionDenied { mut operation } => {
549 Self::prepend_context(&mut operation, context);
550 DbError::PermissionDenied { operation }
551 }
552 DbError::MissingConfiguration { mut parameter } => {
553 Self::prepend_context(&mut parameter, context);
554 DbError::MissingConfiguration { parameter }
555 }
556 DbError::RuntimeError { mut message } => {
557 Self::prepend_context(&mut message, context);
558 DbError::RuntimeError { message }
559 }
560 DbError::ResourceUnavailable {
561 resource_type,
562 mut resource_name,
563 } => {
564 Self::prepend_context(&mut resource_name, context);
565 DbError::ResourceUnavailable {
566 resource_type,
567 resource_name,
568 }
569 }
570 DbError::HardwareError {
571 component,
572 error_code,
573 mut description,
574 } => {
575 Self::prepend_context(&mut description, context);
576 DbError::HardwareError {
577 component,
578 error_code,
579 description,
580 }
581 }
582 DbError::Internal { code, mut message } => {
583 Self::prepend_context(&mut message, context);
584 DbError::Internal { code, message }
585 }
586 #[cfg(feature = "std")]
588 DbError::AttachFailed { mut message } => {
589 Self::prepend_context(&mut message, context);
590 DbError::AttachFailed { message }
591 }
592 #[cfg(feature = "std")]
593 DbError::DetachFailed { mut message } => {
594 Self::prepend_context(&mut message, context);
595 DbError::DetachFailed { message }
596 }
597 #[cfg(feature = "std")]
599 DbError::SetTimeout => DbError::SetTimeout,
600 #[cfg(feature = "std")]
601 DbError::GetTimeout => DbError::GetTimeout,
602 #[cfg(feature = "std")]
603 DbError::RuntimeShutdown => DbError::RuntimeShutdown,
604 DbError::CyclicDependency { .. } => self,
606 DbError::TransformInputNotFound { .. } => self,
607 #[cfg(feature = "std")]
609 DbError::Io { source } => DbError::IoWithContext {
610 context: context.into(),
611 source,
612 },
613 #[cfg(feature = "std")]
614 DbError::Json { source } => DbError::JsonWithContext {
615 context: context.into(),
616 source,
617 },
618 #[cfg(feature = "std")]
620 DbError::IoWithContext {
621 context: mut ctx,
622 source,
623 } => {
624 Self::prepend_context(&mut ctx, context);
625 DbError::IoWithContext {
626 context: ctx,
627 source,
628 }
629 }
630 #[cfg(feature = "std")]
631 DbError::JsonWithContext {
632 context: mut ctx,
633 source,
634 } => {
635 Self::prepend_context(&mut ctx, context);
636 DbError::JsonWithContext {
637 context: ctx,
638 source,
639 }
640 }
641 }
642 }
643
644 #[cfg(feature = "std")]
646 pub fn into_anyhow(self) -> anyhow::Error {
647 self.into()
648 }
649}
650
651pub type DbResult<T> = Result<T, DbError>;
653
654impl From<aimdb_executor::ExecutorError> for DbError {
663 fn from(err: aimdb_executor::ExecutorError) -> Self {
664 use aimdb_executor::ExecutorError;
665
666 match err {
667 ExecutorError::SpawnFailed { message } => {
668 #[cfg(feature = "std")]
669 {
670 DbError::RuntimeError { message }
671 }
672 #[cfg(not(feature = "std"))]
673 {
674 let _ = message; DbError::RuntimeError { _message: () }
676 }
677 }
678 ExecutorError::RuntimeUnavailable { message } => {
679 #[cfg(feature = "std")]
680 {
681 DbError::RuntimeError { message }
682 }
683 #[cfg(not(feature = "std"))]
684 {
685 let _ = message; DbError::RuntimeError { _message: () }
687 }
688 }
689 ExecutorError::TaskJoinFailed { message } => {
690 #[cfg(feature = "std")]
691 {
692 DbError::RuntimeError { message }
693 }
694 #[cfg(not(feature = "std"))]
695 {
696 let _ = message; DbError::RuntimeError { _message: () }
698 }
699 }
700 }
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_error_size_constraint() {
710 let size = core::mem::size_of::<DbError>();
711 assert!(
712 size <= 64,
713 "DbError size ({} bytes) exceeds 64-byte embedded limit",
714 size
715 );
716 }
717
718 #[test]
719 fn test_error_codes() {
720 let connection_error = DbError::ConnectionFailed {
721 #[cfg(feature = "std")]
722 endpoint: "localhost".to_string(),
723 #[cfg(feature = "std")]
724 reason: "timeout".to_string(),
725 #[cfg(not(feature = "std"))]
726 _endpoint: (),
727 #[cfg(not(feature = "std"))]
728 _reason: (),
729 };
730 assert_eq!(connection_error.error_code(), 0x1002);
731 assert_eq!(connection_error.error_category(), 0x1000);
732
733 let buffer_error = DbError::BufferFull {
734 size: 1024,
735 #[cfg(feature = "std")]
736 buffer_name: String::new(),
737 #[cfg(not(feature = "std"))]
738 _buffer_name: (),
739 };
740 assert_eq!(buffer_error.error_code(), 0x2002);
741 }
742
743 #[test]
744 fn test_helper_methods() {
745 let connection_error = DbError::ConnectionFailed {
746 #[cfg(feature = "std")]
747 endpoint: "localhost".to_string(),
748 #[cfg(feature = "std")]
749 reason: "timeout".to_string(),
750 #[cfg(not(feature = "std"))]
751 _endpoint: (),
752 #[cfg(not(feature = "std"))]
753 _reason: (),
754 };
755
756 assert!(connection_error.is_network_error());
757 assert!(!connection_error.is_capacity_error());
758 assert!(!connection_error.is_hardware_error());
759
760 let hardware_error = DbError::hardware_error(2, 404);
761 assert!(hardware_error.is_hardware_error());
762
763 let internal_error = DbError::internal(500);
764 assert!(matches!(
765 internal_error,
766 DbError::Internal { code: 500, .. }
767 ));
768 }
769
770 #[cfg(feature = "std")]
771 #[test]
772 fn test_error_context() {
773 let error = DbError::ConnectionFailed {
774 endpoint: "localhost:5432".to_string(),
775 reason: "timeout".to_string(),
776 }
777 .with_context("Database connection")
778 .with_context("Application startup");
779
780 if let DbError::ConnectionFailed { reason, .. } = error {
781 assert_eq!(reason, "Application startup: Database connection: timeout");
782 } else {
783 panic!("Expected ConnectionFailed");
784 }
785 }
786
787 #[cfg(feature = "std")]
788 #[test]
789 fn test_io_json_conversions() {
790 let io_error = std::io::Error::other("File not found");
791 let db_error: DbError = io_error.into();
792 assert!(matches!(db_error, DbError::Io { .. }));
793
794 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
795 let db_error: DbError = json_error.into();
796 assert!(matches!(db_error, DbError::Json { .. }));
797 }
798}