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 a numeric error code for embedded environments
398    pub const fn error_code(&self) -> u32 {
399        match self {
400            // Network errors: 0x1000-0x1FFF
401            DbError::ConnectionFailed { .. } => 0x1002,
402
403            // Capacity errors: 0x2000-0x2FFF
404            DbError::BufferFull { .. } => 0x2002,
405
406            // Configuration errors: 0x4000-0x4FFF
407            DbError::MissingConfiguration { .. } => 0x4002,
408
409            // Resource errors: 0x5000-0x5FFF
410            DbError::ResourceUnavailable { .. } => 0x5002,
411
412            // Hardware errors: 0x6000-0x6FFF
413            DbError::HardwareError { .. } => 0x6001,
414
415            // Internal errors: 0x7000-0x7FFF
416            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            // Transform / Dependency Graph errors: 0xC000-0xCFFF
428            DbError::CyclicDependency { .. } => 0xC001,
429            DbError::TransformInputNotFound { .. } => 0xC002,
430
431            // I/O errors: 0x8000-0x8FFF (std only)
432            #[cfg(feature = "std")]
433            DbError::Io { .. } => 0x8001,
434            #[cfg(feature = "std")]
435            DbError::IoWithContext { .. } => 0x8002,
436
437            // JSON errors: 0x9000-0x9FFF (std only)
438            #[cfg(feature = "std")]
439            DbError::Json { .. } => 0x9001,
440            #[cfg(feature = "std")]
441            DbError::JsonWithContext { .. } => 0x9002,
442
443            // Buffer operation errors: 0xA000-0xAFFF
444            DbError::BufferLagged { .. } => 0xA001,
445            DbError::BufferClosed { .. } => 0xA002,
446            DbError::BufferEmpty => 0xA003,
447
448            // Sync API errors: 0xB000-0xBFFF (std only)
449            #[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    /// Returns the error category (high nibble)
463    pub const fn error_category(&self) -> u32 {
464        self.error_code() & 0xF000
465    }
466
467    /// Helper to prepend context to a message string
468    #[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    /// Adds additional context to an error (std only)
476    #[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                // No context field, return as-is
518                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            // Sync API errors that support context (std only)
587            #[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            // Sync timeout and shutdown errors don't have context fields (std only)
598            #[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            // Transform errors — return as-is (no mutable context field to prepend)
605            DbError::CyclicDependency { .. } => self,
606            DbError::TransformInputNotFound { .. } => self,
607            // Convert simple I/O and JSON errors to context variants (std only)
608            #[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            // Prepend to existing context for context variants (std only)
619            #[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    /// Converts this error into an anyhow::Error (std only)
645    #[cfg(feature = "std")]
646    pub fn into_anyhow(self) -> anyhow::Error {
647        self.into()
648    }
649}
650
651/// Type alias for Results using DbError
652pub type DbResult<T> = Result<T, DbError>;
653
654// ============================================================================
655// Error Conversions
656// ============================================================================
657
658/// Convert executor errors to database errors
659///
660/// This allows runtime adapters to return `ExecutorError` while the core
661/// database works with `DbError` for consistency across the API.
662impl 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; // Avoid unused warnings
675                    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; // Avoid unused warnings
686                    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; // Avoid unused warnings
697                    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}