Skip to main content

aimdb_core/
error.rs

1//! Error handling for AimDB core operations
2//!
3//! This module provides a streamlined error type system that works across all AimDB
4//! target platforms: MCU (no_std), edge devices and cloud environments.
5//!
6//! # Platform Compatibility
7//!
8//! The error system is designed with conditional compilation to optimize for
9//! different deployment targets:
10//!
11//! - **MCU/Embedded**: Minimal memory footprint with `no_std` compatibility
12//! - **Edge/Desktop**: Rich error context with standard library features
13//! - **Cloud**: Full error chains and debugging capabilities with thiserror integration
14//!
15//! # Error Categories
16//!
17//! The [`DbError`] enum covers only the errors actually used in AimDB:
18//!
19//! - **Network**: Connection failures, timeout errors
20//! - **Buffer**: Full, lagged, or closed buffers
21//! - **Outbox**: Not found, full, closed, or duplicate outbox registration
22//! - **Database**: Record not found, invalid operations
23//! - **Configuration**: Missing required configuration
24//! - **Runtime**: Runtime adapter errors
25//! - **Hardware**: MCU hardware errors (embedded only)
26//! - **I/O & JSON**: Standard library integrations (std only)
27
28#[cfg(feature = "std")]
29use thiserror::Error;
30
31#[cfg(feature = "std")]
32use std::io;
33
34/// Streamlined error type for AimDB operations
35///
36/// Only includes errors that are actually used in the codebase,
37/// removing theoretical/unused error variants for simplicity.
38#[derive(Debug)]
39#[cfg_attr(feature = "std", derive(Error))]
40pub enum DbError {
41    // ===== Network Errors (0x1000-0x1FFF) =====
42    /// Connection or timeout failures
43    #[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    // ===== Buffer Errors (0x2000-0x2FFF & 0xA000-0xAFFF) =====
56    /// Buffer is full and cannot accept more items
57    #[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    /// Consumer lagged behind producer (SPMC ring buffers)
67    #[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    /// Buffer channel has been closed (shutdown)
77    #[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    /// Non-blocking receive found no pending values
86    #[cfg_attr(feature = "std", error("Buffer empty: no pending values"))]
87    BufferEmpty,
88
89    // ===== Database Errors (0x7003-0x7009) =====
90    /// Record type not found in database (legacy, by type name)
91    #[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    /// Record key not found in registry
100    #[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    /// RecordId out of bounds or invalid
109    #[cfg_attr(feature = "std", error("Invalid record ID: {id}"))]
110    InvalidRecordId { id: u32 },
111
112    /// Type mismatch when accessing record by ID
113    #[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    /// Multiple records of same type exist (ambiguous type-only lookup)
126    #[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    /// Duplicate record key during registration
139    #[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    /// Invalid operation attempted
148    #[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    /// Permission denied for operation
161    #[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    // ===== Configuration Errors (0x4000-0x4FFF) =====
170    /// Missing required configuration parameter
171    #[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    // ===== Runtime Errors (0x7002 & 0x5000-0x5FFF) =====
180    /// Runtime execution error (task spawning, scheduling, etc.)
181    #[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    /// Resource temporarily unavailable (used by adapters)
190    #[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    // ===== Hardware Errors (0x6000-0x6FFF) - Embedded Only =====
200    /// Hardware-specific errors for embedded/MCU environments
201    #[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    // ===== Internal Errors (0x7001) =====
215    /// Internal error for unexpected conditions
216    #[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    // ===== Sync API Errors (0xB000-0xBFFF) - std only =====
226    /// Failed to attach database to runtime thread
227    #[cfg(feature = "std")]
228    #[error("Failed to attach database: {message}")]
229    AttachFailed { message: String },
230
231    /// Failed to detach database from runtime thread
232    #[cfg(feature = "std")]
233    #[error("Failed to detach database: {message}")]
234    DetachFailed { message: String },
235
236    /// Timeout while setting a value
237    #[cfg(feature = "std")]
238    #[error("Timeout while setting value")]
239    SetTimeout,
240
241    /// Timeout while getting a value
242    #[cfg(feature = "std")]
243    #[error("Timeout while getting value")]
244    GetTimeout,
245
246    /// Runtime thread has shut down
247    #[cfg(feature = "std")]
248    #[error("Runtime thread has shut down")]
249    RuntimeShutdown,
250
251    // ===== Transform / Dependency Graph Errors =====
252    /// Transform dependency graph contains a cycle
253    #[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    /// Transform input key references a record that was not registered
265    #[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    // ===== Standard Library Integrations (std only) =====
281    /// I/O operation error
282    #[cfg(feature = "std")]
283    #[error("I/O error: {source}")]
284    Io {
285        #[from]
286        source: io::Error,
287    },
288
289    /// I/O operation error with context
290    #[cfg(feature = "std")]
291    #[error("I/O error: {context}: {source}")]
292    IoWithContext {
293        context: String,
294        #[source]
295        source: io::Error,
296    },
297
298    /// JSON serialization error
299    #[cfg(feature = "std")]
300    #[error("JSON error: {source}")]
301    Json {
302        #[from]
303        source: serde_json::Error,
304    },
305
306    /// JSON serialization error with context
307    #[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// ===== no_std Display Implementation =====
317#[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
346// ===== DbError Implementation =====
347impl DbError {
348    // Resource type constants
349    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    /// Creates a hardware error for embedded environments
360    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    /// Creates an internal error with a specific error code
372    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    /// Returns true if this is a network-related error
383    pub fn is_network_error(&self) -> bool {
384        matches!(self, DbError::ConnectionFailed { .. })
385    }
386
387    /// Returns true if this is a capacity-related error
388    pub fn is_capacity_error(&self) -> bool {
389        matches!(self, DbError::BufferFull { .. })
390    }
391
392    /// Returns true if this is a hardware-related error
393    pub fn is_hardware_error(&self) -> bool {
394        matches!(self, DbError::HardwareError { .. })
395    }
396
397    /// Returns true if this is a buffer-related error
398    pub fn is_buffer_error(&self) -> bool {
399        matches!(
400            self,
401            DbError::BufferFull { .. }
402                | DbError::BufferEmpty
403                | DbError::BufferLagged { .. }
404                | DbError::BufferClosed { .. }
405        )
406    }
407
408    /// Returns true if this is a database-related error
409    pub fn is_database_error(&self) -> bool {
410        matches!(
411            self,
412            DbError::RecordNotFound { .. }
413                | DbError::RecordKeyNotFound { .. }
414                | DbError::InvalidRecordId { .. }
415                | DbError::TypeMismatch { .. }
416                | DbError::AmbiguousType { .. }
417                | DbError::DuplicateRecordKey { .. }
418                | DbError::InvalidOperation { .. }
419                | DbError::PermissionDenied { .. }
420        )
421    }
422
423    /// Returns true if this is a configuration-related error
424    pub fn is_configuration_error(&self) -> bool {
425        matches!(self, DbError::MissingConfiguration { .. })
426    }
427
428    /// Returns true if this is a runtime error
429    pub fn is_runtime_error(&self) -> bool {
430        matches!(
431            self,
432            DbError::RuntimeError { .. } | DbError::ResourceUnavailable { .. }
433        )
434    }
435
436    /// Returns true if this is a transform error
437    pub fn is_transform_error(&self) -> bool {
438        matches!(
439            self,
440            DbError::CyclicDependency { .. } | DbError::TransformInputNotFound { .. }
441        )
442    }
443
444    /// Returns true if this is an IO error
445    #[cfg(feature = "std")]
446    pub fn is_io_error(&self) -> bool {
447        matches!(self, DbError::Io { .. } | DbError::IoWithContext { .. })
448    }
449
450    /// Returns true if this is a JSON error
451    #[cfg(feature = "std")]
452    pub fn is_json_error(&self) -> bool {
453        matches!(self, DbError::Json { .. } | DbError::JsonWithContext { .. })
454    }
455
456    /// Returns a numeric error code for embedded environments
457    pub const fn error_code(&self) -> u32 {
458        match self {
459            // Network errors: 0x1000-0x1FFF
460            DbError::ConnectionFailed { .. } => 0x1002,
461
462            // Capacity errors: 0x2000-0x2FFF
463            DbError::BufferFull { .. } => 0x2002,
464
465            // Configuration errors: 0x4000-0x4FFF
466            DbError::MissingConfiguration { .. } => 0x4002,
467
468            // Resource errors: 0x5000-0x5FFF
469            DbError::ResourceUnavailable { .. } => 0x5002,
470
471            // Hardware errors: 0x6000-0x6FFF
472            DbError::HardwareError { .. } => 0x6001,
473
474            // Internal errors: 0x7000-0x7FFF
475            DbError::Internal { .. } => 0x7001,
476            DbError::RuntimeError { .. } => 0x7002,
477            DbError::RecordNotFound { .. } => 0x7003,
478            DbError::InvalidOperation { .. } => 0x7004,
479            DbError::PermissionDenied { .. } => 0x7005,
480            DbError::RecordKeyNotFound { .. } => 0x7006,
481            DbError::InvalidRecordId { .. } => 0x7007,
482            DbError::TypeMismatch { .. } => 0x7008,
483            DbError::AmbiguousType { .. } => 0x7009,
484            DbError::DuplicateRecordKey { .. } => 0x700A,
485
486            // Transform / Dependency Graph errors: 0xC000-0xCFFF
487            DbError::CyclicDependency { .. } => 0xC001,
488            DbError::TransformInputNotFound { .. } => 0xC002,
489
490            // I/O errors: 0x8000-0x8FFF (std only)
491            #[cfg(feature = "std")]
492            DbError::Io { .. } => 0x8001,
493            #[cfg(feature = "std")]
494            DbError::IoWithContext { .. } => 0x8002,
495
496            // JSON errors: 0x9000-0x9FFF (std only)
497            #[cfg(feature = "std")]
498            DbError::Json { .. } => 0x9001,
499            #[cfg(feature = "std")]
500            DbError::JsonWithContext { .. } => 0x9002,
501
502            // Buffer operation errors: 0xA000-0xAFFF
503            DbError::BufferLagged { .. } => 0xA001,
504            DbError::BufferClosed { .. } => 0xA002,
505            DbError::BufferEmpty => 0xA003,
506
507            // Sync API errors: 0xB000-0xBFFF (std only)
508            #[cfg(feature = "std")]
509            DbError::AttachFailed { .. } => 0xB001,
510            #[cfg(feature = "std")]
511            DbError::DetachFailed { .. } => 0xB002,
512            #[cfg(feature = "std")]
513            DbError::SetTimeout => 0xB003,
514            #[cfg(feature = "std")]
515            DbError::GetTimeout => 0xB004,
516            #[cfg(feature = "std")]
517            DbError::RuntimeShutdown => 0xB005,
518        }
519    }
520
521    /// Returns the error category (high nibble)
522    pub const fn error_category(&self) -> u32 {
523        self.error_code() & 0xF000
524    }
525
526    /// Helper to prepend context to a message string
527    #[cfg(feature = "std")]
528    fn prepend_context<S: Into<String>>(existing: &mut String, new_context: S) {
529        let new_context = new_context.into();
530        existing.insert_str(0, ": ");
531        existing.insert_str(0, &new_context);
532    }
533
534    /// Adds additional context to an error (std only)
535    #[cfg(feature = "std")]
536    pub fn with_context<S: Into<String>>(self, context: S) -> Self {
537        match self {
538            DbError::ConnectionFailed {
539                mut reason,
540                endpoint,
541            } => {
542                Self::prepend_context(&mut reason, context);
543                DbError::ConnectionFailed { endpoint, reason }
544            }
545            DbError::BufferFull {
546                size,
547                mut buffer_name,
548            } => {
549                Self::prepend_context(&mut buffer_name, context);
550                DbError::BufferFull { size, buffer_name }
551            }
552            DbError::BufferLagged {
553                lag_count,
554                mut buffer_name,
555            } => {
556                Self::prepend_context(&mut buffer_name, context);
557                DbError::BufferLagged {
558                    lag_count,
559                    buffer_name,
560                }
561            }
562            DbError::BufferClosed { mut buffer_name } => {
563                Self::prepend_context(&mut buffer_name, context);
564                DbError::BufferClosed { buffer_name }
565            }
566            DbError::BufferEmpty => DbError::BufferEmpty,
567            DbError::RecordNotFound { mut record_name } => {
568                Self::prepend_context(&mut record_name, context);
569                DbError::RecordNotFound { record_name }
570            }
571            DbError::RecordKeyNotFound { mut key } => {
572                Self::prepend_context(&mut key, context);
573                DbError::RecordKeyNotFound { key }
574            }
575            DbError::InvalidRecordId { id } => {
576                // No context field, return as-is
577                DbError::InvalidRecordId { id }
578            }
579            DbError::TypeMismatch {
580                record_id,
581                mut expected_type,
582            } => {
583                Self::prepend_context(&mut expected_type, context);
584                DbError::TypeMismatch {
585                    record_id,
586                    expected_type,
587                }
588            }
589            DbError::AmbiguousType {
590                count,
591                mut type_name,
592            } => {
593                Self::prepend_context(&mut type_name, context);
594                DbError::AmbiguousType { count, type_name }
595            }
596            DbError::DuplicateRecordKey { mut key } => {
597                Self::prepend_context(&mut key, context);
598                DbError::DuplicateRecordKey { key }
599            }
600            DbError::InvalidOperation {
601                operation,
602                mut reason,
603            } => {
604                Self::prepend_context(&mut reason, context);
605                DbError::InvalidOperation { operation, reason }
606            }
607            DbError::PermissionDenied { mut operation } => {
608                Self::prepend_context(&mut operation, context);
609                DbError::PermissionDenied { operation }
610            }
611            DbError::MissingConfiguration { mut parameter } => {
612                Self::prepend_context(&mut parameter, context);
613                DbError::MissingConfiguration { parameter }
614            }
615            DbError::RuntimeError { mut message } => {
616                Self::prepend_context(&mut message, context);
617                DbError::RuntimeError { message }
618            }
619            DbError::ResourceUnavailable {
620                resource_type,
621                mut resource_name,
622            } => {
623                Self::prepend_context(&mut resource_name, context);
624                DbError::ResourceUnavailable {
625                    resource_type,
626                    resource_name,
627                }
628            }
629            DbError::HardwareError {
630                component,
631                error_code,
632                mut description,
633            } => {
634                Self::prepend_context(&mut description, context);
635                DbError::HardwareError {
636                    component,
637                    error_code,
638                    description,
639                }
640            }
641            DbError::Internal { code, mut message } => {
642                Self::prepend_context(&mut message, context);
643                DbError::Internal { code, message }
644            }
645            // Sync API errors that support context (std only)
646            #[cfg(feature = "std")]
647            DbError::AttachFailed { mut message } => {
648                Self::prepend_context(&mut message, context);
649                DbError::AttachFailed { message }
650            }
651            #[cfg(feature = "std")]
652            DbError::DetachFailed { mut message } => {
653                Self::prepend_context(&mut message, context);
654                DbError::DetachFailed { message }
655            }
656            // Sync timeout and shutdown errors don't have context fields (std only)
657            #[cfg(feature = "std")]
658            DbError::SetTimeout => DbError::SetTimeout,
659            #[cfg(feature = "std")]
660            DbError::GetTimeout => DbError::GetTimeout,
661            #[cfg(feature = "std")]
662            DbError::RuntimeShutdown => DbError::RuntimeShutdown,
663            // Transform errors — return as-is (no mutable context field to prepend)
664            DbError::CyclicDependency { .. } => self,
665            DbError::TransformInputNotFound { .. } => self,
666            // Convert simple I/O and JSON errors to context variants (std only)
667            #[cfg(feature = "std")]
668            DbError::Io { source } => DbError::IoWithContext {
669                context: context.into(),
670                source,
671            },
672            #[cfg(feature = "std")]
673            DbError::Json { source } => DbError::JsonWithContext {
674                context: context.into(),
675                source,
676            },
677            // Prepend to existing context for context variants (std only)
678            #[cfg(feature = "std")]
679            DbError::IoWithContext {
680                context: mut ctx,
681                source,
682            } => {
683                Self::prepend_context(&mut ctx, context);
684                DbError::IoWithContext {
685                    context: ctx,
686                    source,
687                }
688            }
689            #[cfg(feature = "std")]
690            DbError::JsonWithContext {
691                context: mut ctx,
692                source,
693            } => {
694                Self::prepend_context(&mut ctx, context);
695                DbError::JsonWithContext {
696                    context: ctx,
697                    source,
698                }
699            }
700        }
701    }
702
703    /// Converts this error into an anyhow::Error (std only)
704    #[cfg(feature = "std")]
705    pub fn into_anyhow(self) -> anyhow::Error {
706        self.into()
707    }
708}
709
710/// Type alias for Results using DbError
711pub type DbResult<T> = Result<T, DbError>;
712
713// ============================================================================
714// Error Conversions
715// ============================================================================
716
717/// Convert executor errors to database errors
718///
719/// This allows runtime adapters to return `ExecutorError` while the core
720/// database works with `DbError` for consistency across the API.
721impl From<aimdb_executor::ExecutorError> for DbError {
722    fn from(err: aimdb_executor::ExecutorError) -> Self {
723        use aimdb_executor::ExecutorError;
724
725        match err {
726            ExecutorError::SpawnFailed { message } => {
727                #[cfg(feature = "std")]
728                {
729                    DbError::RuntimeError { message }
730                }
731                #[cfg(not(feature = "std"))]
732                {
733                    let _ = message; // Avoid unused warnings
734                    DbError::RuntimeError { _message: () }
735                }
736            }
737            ExecutorError::RuntimeUnavailable { message } => {
738                #[cfg(feature = "std")]
739                {
740                    DbError::RuntimeError { message }
741                }
742                #[cfg(not(feature = "std"))]
743                {
744                    let _ = message; // Avoid unused warnings
745                    DbError::RuntimeError { _message: () }
746                }
747            }
748            ExecutorError::TaskJoinFailed { message } => {
749                #[cfg(feature = "std")]
750                {
751                    DbError::RuntimeError { message }
752                }
753                #[cfg(not(feature = "std"))]
754                {
755                    let _ = message; // Avoid unused warnings
756                    DbError::RuntimeError { _message: () }
757                }
758            }
759            ExecutorError::QueueClosed => {
760                #[cfg(feature = "std")]
761                {
762                    DbError::RuntimeError {
763                        message: "join queue closed".to_string(),
764                    }
765                }
766                #[cfg(not(feature = "std"))]
767                {
768                    DbError::RuntimeError { _message: () }
769                }
770            }
771        }
772    }
773}
774
775#[cfg(test)]
776mod tests {
777    use super::*;
778
779    #[test]
780    fn test_error_size_constraint() {
781        let size = core::mem::size_of::<DbError>();
782        assert!(
783            size <= 64,
784            "DbError size ({} bytes) exceeds 64-byte embedded limit",
785            size
786        );
787    }
788
789    #[test]
790    fn test_error_codes() {
791        let connection_error = DbError::ConnectionFailed {
792            #[cfg(feature = "std")]
793            endpoint: "localhost".to_string(),
794            #[cfg(feature = "std")]
795            reason: "timeout".to_string(),
796            #[cfg(not(feature = "std"))]
797            _endpoint: (),
798            #[cfg(not(feature = "std"))]
799            _reason: (),
800        };
801        assert_eq!(connection_error.error_code(), 0x1002);
802        assert_eq!(connection_error.error_category(), 0x1000);
803
804        let buffer_error = DbError::BufferFull {
805            size: 1024,
806            #[cfg(feature = "std")]
807            buffer_name: String::new(),
808            #[cfg(not(feature = "std"))]
809            _buffer_name: (),
810        };
811        assert_eq!(buffer_error.error_code(), 0x2002);
812    }
813
814    #[test]
815    fn test_helper_methods() {
816        let connection_error = DbError::ConnectionFailed {
817            #[cfg(feature = "std")]
818            endpoint: "localhost".to_string(),
819            #[cfg(feature = "std")]
820            reason: "timeout".to_string(),
821            #[cfg(not(feature = "std"))]
822            _endpoint: (),
823            #[cfg(not(feature = "std"))]
824            _reason: (),
825        };
826
827        assert!(connection_error.is_network_error());
828        assert!(!connection_error.is_capacity_error());
829        assert!(!connection_error.is_hardware_error());
830
831        let hardware_error = DbError::hardware_error(2, 404);
832        assert!(hardware_error.is_hardware_error());
833
834        let internal_error = DbError::internal(500);
835        assert!(matches!(
836            internal_error,
837            DbError::Internal { code: 500, .. }
838        ));
839    }
840
841    #[cfg(feature = "std")]
842    #[test]
843    fn test_error_context() {
844        let error = DbError::ConnectionFailed {
845            endpoint: "localhost:5432".to_string(),
846            reason: "timeout".to_string(),
847        }
848        .with_context("Database connection")
849        .with_context("Application startup");
850
851        if let DbError::ConnectionFailed { reason, .. } = error {
852            assert_eq!(reason, "Application startup: Database connection: timeout");
853        } else {
854            panic!("Expected ConnectionFailed");
855        }
856    }
857
858    #[cfg(feature = "std")]
859    #[test]
860    fn test_io_json_conversions() {
861        let io_error = std::io::Error::other("File not found");
862        let db_error: DbError = io_error.into();
863        assert!(matches!(db_error, DbError::Io { .. }));
864
865        let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
866        let db_error: DbError = json_error.into();
867        assert!(matches!(db_error, DbError::Json { .. }));
868    }
869
870    #[test]
871    fn test_buffer_empty() {
872        let buffer = DbError::BufferEmpty;
873        assert!(buffer.is_buffer_error());
874        assert!(!buffer.is_network_error());
875    }
876
877    #[cfg(feature = "std")]
878    #[test]
879    fn test_configuration_error() {
880        let configuration = DbError::MissingConfiguration {
881            parameter: "my_parameter".to_string(),
882        };
883        assert!(configuration.is_configuration_error());
884        assert!(!configuration.is_buffer_error());
885    }
886
887    #[cfg(feature = "std")]
888    #[test]
889    fn test_runtime_error() {
890        let runtime = DbError::RuntimeError {
891            message: "Hello, World".to_string(),
892        };
893        assert!(runtime.is_runtime_error());
894        assert!(!runtime.is_buffer_error());
895    }
896
897    #[test]
898    fn test_database_error() {
899        let database = DbError::InvalidRecordId { id: 5 };
900        assert!(database.is_database_error());
901        assert!(!database.is_buffer_error());
902    }
903
904    #[cfg(feature = "std")]
905    #[test]
906    fn test_transform_error() {
907        let transform = DbError::CyclicDependency {
908            records: vec!["Hello, World!".to_string()],
909        };
910        assert!(transform.is_transform_error());
911        assert!(!transform.is_buffer_error());
912    }
913
914    #[cfg(feature = "std")]
915    #[test]
916    fn test_io_error() {
917        let io_error = std::io::Error::other("test");
918        let db_error: DbError = io_error.into();
919        assert!(db_error.is_io_error());
920        assert!(!db_error.is_buffer_error());
921    }
922
923    #[cfg(feature = "std")]
924    #[test]
925    fn test_json_error() {
926        let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
927        let db_error: DbError = json_error.into();
928        assert!(db_error.is_json_error());
929        assert!(!db_error.is_buffer_error());
930    }
931}