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    // ===== Database Errors (0x7003-0x7009) =====
86    /// Record type not found in database (legacy, by type name)
87    #[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    /// Record key not found in registry
96    #[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    /// RecordId out of bounds or invalid
105    #[cfg_attr(feature = "std", error("Invalid record ID: {id}"))]
106    InvalidRecordId { id: u32 },
107
108    /// Type mismatch when accessing record by ID
109    #[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    /// Multiple records of same type exist (ambiguous type-only lookup)
122    #[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    /// Duplicate record key during registration
135    #[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    /// Invalid operation attempted
144    #[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    /// Permission denied for operation
157    #[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    // ===== Configuration Errors (0x4000-0x4FFF) =====
166    /// Missing required configuration parameter
167    #[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    // ===== Runtime Errors (0x7002 & 0x5000-0x5FFF) =====
176    /// Runtime execution error (task spawning, scheduling, etc.)
177    #[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    /// Resource temporarily unavailable (used by adapters)
186    #[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    // ===== Hardware Errors (0x6000-0x6FFF) - Embedded Only =====
196    /// Hardware-specific errors for embedded/MCU environments
197    #[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    // ===== Internal Errors (0x7001) =====
211    /// Internal error for unexpected conditions
212    #[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    // ===== Sync API Errors (0xB000-0xBFFF) - std only =====
222    /// Failed to attach database to runtime thread
223    #[cfg(feature = "std")]
224    #[error("Failed to attach database: {message}")]
225    AttachFailed { message: String },
226
227    /// Failed to detach database from runtime thread
228    #[cfg(feature = "std")]
229    #[error("Failed to detach database: {message}")]
230    DetachFailed { message: String },
231
232    /// Timeout while setting a value
233    #[cfg(feature = "std")]
234    #[error("Timeout while setting value")]
235    SetTimeout,
236
237    /// Timeout while getting a value
238    #[cfg(feature = "std")]
239    #[error("Timeout while getting value")]
240    GetTimeout,
241
242    /// Runtime thread has shut down
243    #[cfg(feature = "std")]
244    #[error("Runtime thread has shut down")]
245    RuntimeShutdown,
246
247    // ===== Standard Library Integrations (std only) =====
248    /// I/O operation error
249    #[cfg(feature = "std")]
250    #[error("I/O error: {source}")]
251    Io {
252        #[from]
253        source: io::Error,
254    },
255
256    /// I/O operation error with context
257    #[cfg(feature = "std")]
258    #[error("I/O error: {context}: {source}")]
259    IoWithContext {
260        context: String,
261        #[source]
262        source: io::Error,
263    },
264
265    /// JSON serialization error
266    #[cfg(feature = "std")]
267    #[error("JSON error: {source}")]
268    Json {
269        #[from]
270        source: serde_json::Error,
271    },
272
273    /// JSON serialization error with context
274    #[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// ===== no_std Display Implementation =====
284#[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
310// ===== DbError Implementation =====
311impl DbError {
312    // Resource type constants
313    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    /// Creates a hardware error for embedded environments
324    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    /// Creates an internal error with a specific error code
336    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    /// Returns true if this is a network-related error
347    pub fn is_network_error(&self) -> bool {
348        matches!(self, DbError::ConnectionFailed { .. })
349    }
350
351    /// Returns true if this is a capacity-related error  
352    pub fn is_capacity_error(&self) -> bool {
353        matches!(self, DbError::BufferFull { .. })
354    }
355
356    /// Returns true if this is a hardware-related error
357    pub fn is_hardware_error(&self) -> bool {
358        matches!(self, DbError::HardwareError { .. })
359    }
360
361    /// Returns a numeric error code for embedded environments
362    pub const fn error_code(&self) -> u32 {
363        match self {
364            // Network errors: 0x1000-0x1FFF
365            DbError::ConnectionFailed { .. } => 0x1002,
366
367            // Capacity errors: 0x2000-0x2FFF
368            DbError::BufferFull { .. } => 0x2002,
369
370            // Configuration errors: 0x4000-0x4FFF
371            DbError::MissingConfiguration { .. } => 0x4002,
372
373            // Resource errors: 0x5000-0x5FFF
374            DbError::ResourceUnavailable { .. } => 0x5002,
375
376            // Hardware errors: 0x6000-0x6FFF
377            DbError::HardwareError { .. } => 0x6001,
378
379            // Internal errors: 0x7000-0x7FFF
380            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            // I/O errors: 0x8000-0x8FFF (std only)
392            #[cfg(feature = "std")]
393            DbError::Io { .. } => 0x8001,
394            #[cfg(feature = "std")]
395            DbError::IoWithContext { .. } => 0x8002,
396
397            // JSON errors: 0x9000-0x9FFF (std only)
398            #[cfg(feature = "std")]
399            DbError::Json { .. } => 0x9001,
400            #[cfg(feature = "std")]
401            DbError::JsonWithContext { .. } => 0x9002,
402
403            // Buffer operation errors: 0xA000-0xAFFF
404            DbError::BufferLagged { .. } => 0xA001,
405            DbError::BufferClosed { .. } => 0xA002,
406
407            // Sync API errors: 0xB000-0xBFFF (std only)
408            #[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    /// Returns the error category (high nibble)
422    pub const fn error_category(&self) -> u32 {
423        self.error_code() & 0xF000
424    }
425
426    /// Helper to prepend context to a message string
427    #[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    /// Adds additional context to an error (std only)
435    #[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                // No context field, return as-is
476                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            // Sync API errors that support context (std only)
545            #[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            // Sync timeout and shutdown errors don't have context fields (std only)
556            #[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            // Convert simple I/O and JSON errors to context variants (std only)
563            #[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            // Prepend to existing context for context variants (std only)
574            #[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    /// Converts this error into an anyhow::Error (std only)
600    #[cfg(feature = "std")]
601    pub fn into_anyhow(self) -> anyhow::Error {
602        self.into()
603    }
604}
605
606/// Type alias for Results using DbError
607pub type DbResult<T> = Result<T, DbError>;
608
609// ============================================================================
610// Error Conversions
611// ============================================================================
612
613/// Convert executor errors to database errors
614///
615/// This allows runtime adapters to return `ExecutorError` while the core
616/// database works with `DbError` for consistency across the API.
617impl 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; // Avoid unused warnings
630                    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; // Avoid unused warnings
641                    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; // Avoid unused warnings
652                    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}