saorsa_core/
error.rs

1// Copyright (c) 2025 Saorsa Labs Limited
2
3// This software is dual-licensed under:
4// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5// - Commercial License
6//
7// For AGPL-3.0 license, see LICENSE-AGPL-3.0
8// For commercial licensing, contact: saorsalabs@gmail.com
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under these licenses is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17// GNU Affero General Public License for more details.
18
19// You should have received a copy of the GNU Affero General Public License
20// along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22//! Comprehensive error handling framework for P2P Foundation
23//!
24//! This module provides a zero-panic error handling system designed to replace 568 unwrap() calls
25//! throughout the codebase with proper error propagation and context.
26//!
27//! # Features
28//!
29//! - **Type-safe error hierarchy**: Custom error types for all subsystems
30//! - **Zero-cost abstractions**: Optimized for performance with Cow<'static, str>
31//! - **Context propagation**: Rich error context without heap allocations
32//! - **Structured logging**: JSON-based error reporting for production monitoring
33//! - **Anyhow integration**: Seamless integration for application-level errors
34//! - **Recovery patterns**: Built-in retry and circuit breaker support
35//!
36//! # Usage Examples
37//!
38//! ## Basic Error Handling
39//!
40//! ```rust
41//! use p2p_core::error::{P2PError, P2pResult, ErrorContext};
42//!
43//! fn connect_to_peer(addr: SocketAddr) -> P2pResult<Connection> {
44//!     // Instead of: socket.connect(addr).unwrap()
45//!     let conn = socket.connect(addr)
46//!         .map_err(|e| NetworkError::ConnectionFailed {
47//!             addr,
48//!             reason: e.to_string().into(),
49//!         })?;
50//!
51//!     Ok(conn)
52//! }
53//! ```
54//!
55//! ## Adding Context
56//!
57//! ```rust
58//! use p2p_core::error::{P2PError, P2pResult, ErrorContext};
59//!
60//! fn load_config(path: &str) -> P2pResult<Config> {
61//!     std::fs::read_to_string(path)
62//!         .context("Failed to read config file")?
63//!         .parse()
64//!         .context("Failed to parse config")
65//! }
66//! ```
67//!
68//! ## Structured Error Logging
69//!
70//! ```rust
71//! use p2p_core::error::{P2PError, ErrorReporting};
72//!
73//! fn handle_error(err: P2PError) {
74//!     // Automatically logs with appropriate level and context
75//!     err.log();
76//!
77//!     // Or get structured log for custom handling
78//!     let log_entry = err.to_error_log();
79//!     send_to_monitoring_system(&log_entry);
80//! }
81//! ```
82//!
83//! ## Migration from unwrap()
84//!
85//! ```rust
86//! // Before:
87//! let value = some_operation().unwrap();
88//!
89//! // After:
90//! let value = some_operation()
91//!     .context("Failed to perform operation")?;
92//!
93//! // For performance-critical paths with known bounds:
94//! let value = some_operation()
95//!     .ok_or_else(|| P2PError::Internal("Operation failed".into()))?;
96//! ```
97
98use serde::{Deserialize, Serialize};
99use smallvec::SmallVec;
100use std::borrow::Cow;
101use std::collections::HashMap;
102use std::io;
103use std::net::SocketAddr;
104use std::time::Duration;
105use thiserror::Error;
106
107// Metrics imports would go here when implemented
108// #[cfg(feature = "metrics")]
109// use prometheus::{IntCounterVec, register_int_counter_vec};
110
111/// Core error type for the P2P Foundation library
112#[derive(Debug, Error)]
113pub enum P2PError {
114    // Network errors
115    #[error("Network error: {0}")]
116    Network(#[from] NetworkError),
117
118    // DHT errors
119    #[error("DHT error: {0}")]
120    Dht(#[from] DhtError),
121
122    // Identity errors
123    #[error("Identity error: {0}")]
124    Identity(#[from] IdentityError),
125
126    // Cryptography errors
127    #[error("Cryptography error: {0}")]
128    Crypto(#[from] CryptoError),
129
130    // Storage errors
131    #[error("Storage error: {0}")]
132    Storage(#[from] StorageError),
133
134    // Transport errors
135    #[error("Transport error: {0}")]
136    Transport(#[from] TransportError),
137
138    // Configuration errors
139    #[error("Configuration error: {0}")]
140    Config(#[from] ConfigError),
141
142    // Security errors
143    #[error("Security error: {0}")]
144    Security(#[from] SecurityError),
145
146    // Bootstrap errors
147    #[error("Bootstrap error: {0}")]
148    Bootstrap(#[from] BootstrapError),
149
150    // Generic IO error
151    #[error("IO error: {0}")]
152    Io(#[from] io::Error),
153
154    // Serialization/Deserialization errors
155    #[error("Serialization error: {0}")]
156    Serialization(Cow<'static, str>),
157
158    // Validation errors
159    #[error("Validation error: {0}")]
160    Validation(Cow<'static, str>),
161
162    // Timeout errors
163    #[error("Operation timed out after {0:?}")]
164    Timeout(Duration),
165
166    // Resource exhaustion
167    #[error("Resource exhausted: {0}")]
168    ResourceExhausted(Cow<'static, str>),
169
170    // Generic internal error
171    #[error("Internal error: {0}")]
172    Internal(Cow<'static, str>),
173
174    // Encoding errors
175    #[error("Encoding error: {0}")]
176    Encoding(Cow<'static, str>),
177
178    // Record too large errors
179    #[error("Record too large: {0} bytes (max 512)")]
180    RecordTooLarge(usize),
181
182    // Time-related error
183    #[error("Time error")]
184    TimeError,
185
186    // Invalid input parameter
187    #[error("Invalid input: {0}")]
188    InvalidInput(String),
189
190    // WebRTC bridge errors
191    #[error("WebRTC error: {0}")]
192    WebRtcError(String),
193}
194
195/// Network-related errors
196#[derive(Debug, Error)]
197pub enum NetworkError {
198    #[error("Connection failed to {addr}: {reason}")]
199    ConnectionFailed {
200        addr: SocketAddr,
201        reason: Cow<'static, str>,
202    },
203
204    #[error("Connection closed unexpectedly for peer: {peer_id}")]
205    ConnectionClosed { peer_id: Cow<'static, str> },
206
207    #[error("Invalid network address: {0}")]
208    InvalidAddress(Cow<'static, str>),
209
210    #[error("Peer not found: {0}")]
211    PeerNotFound(Cow<'static, str>),
212
213    #[error("Peer disconnected - peer: {peer}, reason: {reason}")]
214    PeerDisconnected { peer: String, reason: String },
215
216    #[error("Network timeout")]
217    Timeout,
218
219    #[error("Too many connections")]
220    TooManyConnections,
221
222    #[error("Protocol error: {0}")]
223    ProtocolError(Cow<'static, str>),
224
225    #[error("Bind error: {0}")]
226    BindError(Cow<'static, str>),
227}
228
229/// DHT-related errors
230#[derive(Debug, Error)]
231pub enum DhtError {
232    #[error("Key not found: {0}")]
233    KeyNotFound(Cow<'static, str>),
234
235    #[error("Store operation failed: {0}")]
236    StoreFailed(Cow<'static, str>),
237
238    #[error("Invalid key format: {0}")]
239    InvalidKey(Cow<'static, str>),
240
241    #[error("Routing table full")]
242    RoutingTableFull,
243
244    #[error("No suitable peers found")]
245    NoPeersFound,
246
247    #[error("Replication failed: {0}")]
248    ReplicationFailed(Cow<'static, str>),
249
250    #[error("Query timeout")]
251    QueryTimeout,
252
253    #[error("Routing error: {0}")]
254    RoutingError(Cow<'static, str>),
255
256    #[error("Storage failed: {0}")]
257    StorageFailed(Cow<'static, str>),
258
259    #[error("Insufficient replicas: {0}")]
260    InsufficientReplicas(Cow<'static, str>),
261}
262
263/// Identity-related errors
264#[derive(Debug, Error)]
265pub enum IdentityError {
266    #[error("Invalid three-word address: {0}")]
267    InvalidThreeWordAddress(Cow<'static, str>),
268
269    #[error("Invalid four-word address: {0}")]
270    InvalidFourWordAddress(Cow<'static, str>),
271
272    #[error("Identity not found: {0}")]
273    IdentityNotFound(Cow<'static, str>),
274
275    #[error("Identity already exists: {0}")]
276    IdentityExists(Cow<'static, str>),
277
278    #[error("Invalid signature")]
279    InvalidSignature,
280
281    #[error("Invalid canonical bytes")]
282    InvalidCanonicalBytes,
283
284    #[error("Membership conflict")]
285    MembershipConflict,
286
287    #[error("Missing group key")]
288    MissingGroupKey,
289
290    #[error("Website root update refused")]
291    WebsiteRootUpdateRefused,
292
293    #[error("Key derivation failed: {0}")]
294    KeyDerivationFailed(Cow<'static, str>),
295
296    #[error("Permission denied")]
297    PermissionDenied,
298
299    #[error("Invalid peer ID: {0}")]
300    InvalidPeerId(Cow<'static, str>),
301
302    #[error("Invalid format: {0}")]
303    InvalidFormat(Cow<'static, str>),
304
305    #[error("System time error: {0}")]
306    SystemTime(Cow<'static, str>),
307
308    #[error("Not found: {0}")]
309    NotFound(Cow<'static, str>),
310
311    #[error("Verification failed: {0}")]
312    VerificationFailed(Cow<'static, str>),
313
314    #[error("Insufficient entropy")]
315    InsufficientEntropy,
316
317    #[error("Access denied: {0}")]
318    AccessDenied(Cow<'static, str>),
319}
320
321/// Cryptography-related errors
322#[derive(Debug, Error)]
323pub enum CryptoError {
324    #[error("Encryption failed: {0}")]
325    EncryptionFailed(Cow<'static, str>),
326
327    #[error("Decryption failed: {0}")]
328    DecryptionFailed(Cow<'static, str>),
329
330    #[error("Invalid key length: expected {expected}, got {actual}")]
331    InvalidKeyLength { expected: usize, actual: usize },
332
333    #[error("Signature verification failed")]
334    SignatureVerificationFailed,
335
336    #[error("Key generation failed: {0}")]
337    KeyGenerationFailed(Cow<'static, str>),
338
339    #[error("Invalid public key")]
340    InvalidPublicKey,
341
342    #[error("Invalid private key")]
343    InvalidPrivateKey,
344
345    #[error("HKDF expansion failed: {0}")]
346    HkdfError(Cow<'static, str>),
347}
348
349/// Storage-related errors
350#[derive(Debug, Error)]
351pub enum StorageError {
352    #[error("Database error: {0}")]
353    Database(Cow<'static, str>),
354
355    #[error("Disk full")]
356    DiskFull,
357
358    #[error("Corrupt data: {0}")]
359    CorruptData(Cow<'static, str>),
360
361    #[error("Storage path not found: {0}")]
362    PathNotFound(Cow<'static, str>),
363
364    #[error("Permission denied: {0}")]
365    PermissionDenied(Cow<'static, str>),
366
367    #[error("Lock acquisition failed")]
368    LockFailed,
369
370    #[error("Lock poisoned: {0}")]
371    LockPoisoned(Cow<'static, str>),
372
373    #[error("File not found: {0}")]
374    FileNotFound(Cow<'static, str>),
375
376    #[error("Corruption detected: {0}")]
377    CorruptionDetected(Cow<'static, str>),
378}
379
380/// Transport-related errors
381#[derive(Debug, Error)]
382pub enum TransportError {
383    #[error("QUIC error: {0}")]
384    Quic(Cow<'static, str>),
385
386    #[error("TCP error: {0}")]
387    Tcp(Cow<'static, str>),
388
389    #[error("Invalid transport configuration: {0}")]
390    InvalidConfig(Cow<'static, str>),
391
392    #[error("Transport not supported: {0}")]
393    NotSupported(Cow<'static, str>),
394
395    #[error("Stream error: {0}")]
396    StreamError(Cow<'static, str>),
397
398    #[error("Certificate error: {0}")]
399    CertificateError(Cow<'static, str>),
400
401    #[error("Setup failed: {0}")]
402    SetupFailed(Cow<'static, str>),
403
404    #[error("Connection failed to {addr}: {reason}")]
405    ConnectionFailed {
406        addr: SocketAddr,
407        reason: Cow<'static, str>,
408    },
409
410    #[error("Bind error: {0}")]
411    BindError(Cow<'static, str>),
412
413    #[error("Accept failed: {0}")]
414    AcceptFailed(Cow<'static, str>),
415
416    #[error("Not listening")]
417    NotListening,
418
419    #[error("Not initialized")]
420    NotInitialized,
421}
422
423/// Configuration-related errors
424#[derive(Debug, Error)]
425pub enum ConfigError {
426    #[error("Missing required field: {0}")]
427    MissingField(Cow<'static, str>),
428
429    #[error("Invalid value for {field}: {reason}")]
430    InvalidValue {
431        field: Cow<'static, str>,
432        reason: Cow<'static, str>,
433    },
434
435    #[error("Configuration file not found: {0}")]
436    FileNotFound(Cow<'static, str>),
437
438    #[error("Parse error: {0}")]
439    ParseError(Cow<'static, str>),
440
441    #[error("Validation failed: {0}")]
442    ValidationFailed(Cow<'static, str>),
443
444    #[error("IO error for {path}: {source}")]
445    IoError {
446        path: Cow<'static, str>,
447        #[source]
448        source: std::io::Error,
449    },
450}
451
452/// Security-related errors
453#[derive(Debug, Error)]
454pub enum SecurityError {
455    #[error("Authentication failed")]
456    AuthenticationFailed,
457
458    #[error("Authorization denied")]
459    AuthorizationDenied,
460
461    #[error("Invalid credentials")]
462    InvalidCredentials,
463
464    #[error("Certificate error: {0}")]
465    CertificateError(Cow<'static, str>),
466
467    #[error("Encryption failed: {0}")]
468    EncryptionFailed(Cow<'static, str>),
469
470    #[error("Decryption failed: {0}")]
471    DecryptionFailed(Cow<'static, str>),
472
473    #[error("Invalid key: {0}")]
474    InvalidKey(Cow<'static, str>),
475
476    #[error("Signature verification failed: {0}")]
477    SignatureVerificationFailed(Cow<'static, str>),
478
479    #[error("Key generation failed: {0}")]
480    KeyGenerationFailed(Cow<'static, str>),
481
482    #[error("Authorization failed: {0}")]
483    AuthorizationFailed(Cow<'static, str>),
484}
485
486/// Bootstrap-related errors
487#[derive(Debug, Error)]
488pub enum BootstrapError {
489    #[error("No bootstrap nodes available")]
490    NoBootstrapNodes,
491
492    #[error("Bootstrap failed: {0}")]
493    BootstrapFailed(Cow<'static, str>),
494
495    #[error("Invalid bootstrap node: {0}")]
496    InvalidBootstrapNode(Cow<'static, str>),
497
498    #[error("Bootstrap timeout")]
499    BootstrapTimeout,
500
501    #[error("Cache error: {0}")]
502    CacheError(Cow<'static, str>),
503
504    #[error("Invalid data: {0}")]
505    InvalidData(Cow<'static, str>),
506
507    #[error("Rate limited: {0}")]
508    RateLimited(Cow<'static, str>),
509}
510
511/// Geographic validation errors for connection rejection
512#[derive(Debug, Error, Clone)]
513pub enum GeoRejectionError {
514    #[error("Peer from blocked region: {0}")]
515    BlockedRegion(String),
516
517    #[error("Geographic diversity violation in region {region} (ratio: {current_ratio:.1}%)")]
518    DiversityViolation { region: String, current_ratio: f64 },
519
520    #[error("Region lookup failed: {0}")]
521    LookupFailed(String),
522}
523
524/// Geographic enforcement mode
525#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
526pub enum GeoEnforcementMode {
527    /// Audit mode - log violations but allow connections
528    LogOnly,
529    /// Strict mode - reject connections that violate rules
530    #[default]
531    Strict,
532}
533
534/// Configuration for geographic diversity enforcement
535#[derive(Debug, Clone)]
536pub struct GeographicConfig {
537    /// Minimum number of regions required (default: 3)
538    pub min_regions: usize,
539    /// Maximum ratio of peers from a single region (default: 0.4 = 40%)
540    pub max_single_region_ratio: f64,
541    /// Regions to outright block
542    pub blocked_regions: Vec<String>,
543    /// Enforcement mode (LogOnly or Strict)
544    pub enforcement_mode: GeoEnforcementMode,
545}
546
547impl Default for GeographicConfig {
548    fn default() -> Self {
549        Self {
550            min_regions: 3,
551            max_single_region_ratio: 0.4,
552            blocked_regions: Vec::new(),
553            enforcement_mode: GeoEnforcementMode::Strict,
554        }
555    }
556}
557
558impl GeographicConfig {
559    /// Create a new config with strict enforcement (default)
560    pub fn strict() -> Self {
561        Self::default()
562    }
563
564    /// Create a config for logging only (no rejection)
565    pub fn log_only() -> Self {
566        Self {
567            enforcement_mode: GeoEnforcementMode::LogOnly,
568            ..Default::default()
569        }
570    }
571
572    /// Add a blocked region
573    pub fn with_blocked_region(mut self, region: impl Into<String>) -> Self {
574        self.blocked_regions.push(region.into());
575        self
576    }
577
578    /// Set maximum single region ratio
579    pub fn with_max_ratio(mut self, ratio: f64) -> Self {
580        self.max_single_region_ratio = ratio;
581        self
582    }
583}
584
585/// Result type alias for P2P operations
586pub type P2pResult<T> = Result<T, P2PError>;
587
588// ===== Recovery patterns =====
589
590/// Trait for errors that can be recovered from with retry
591pub trait Recoverable {
592    /// Check if this error is transient and can be retried
593    fn is_transient(&self) -> bool;
594
595    /// Suggested delay before retry
596    fn suggested_retry_after(&self) -> Option<Duration>;
597
598    /// Maximum number of retries recommended
599    fn max_retries(&self) -> usize;
600}
601
602impl Recoverable for P2PError {
603    fn is_transient(&self) -> bool {
604        match self {
605            P2PError::Network(NetworkError::ConnectionFailed { .. }) => true,
606            P2PError::Network(NetworkError::Timeout) => true,
607            P2PError::Transport(TransportError::ConnectionFailed { .. }) => true,
608            P2PError::Dht(DhtError::QueryTimeout) => true,
609            P2PError::Timeout(_) => true,
610            P2PError::ResourceExhausted(_) => true,
611            P2PError::Io(err) => matches!(
612                err.kind(),
613                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut | io::ErrorKind::Interrupted
614            ),
615            _ => false,
616        }
617    }
618
619    fn suggested_retry_after(&self) -> Option<Duration> {
620        match self {
621            P2PError::Network(NetworkError::Timeout) => Some(Duration::from_secs(5)),
622            P2PError::Timeout(duration) => Some(*duration * 2),
623            P2PError::ResourceExhausted(_) => Some(Duration::from_secs(30)),
624            P2PError::Transport(TransportError::ConnectionFailed { .. }) => {
625                Some(Duration::from_secs(1))
626            }
627            _ => None,
628        }
629    }
630
631    fn max_retries(&self) -> usize {
632        match self {
633            P2PError::Network(NetworkError::ConnectionFailed { .. }) => 3,
634            P2PError::Transport(TransportError::ConnectionFailed { .. }) => 3,
635            P2PError::Timeout(_) => 2,
636            P2PError::ResourceExhausted(_) => 1,
637            _ => 0,
638        }
639    }
640}
641
642/// Extension trait for adding context to errors
643pub trait ErrorContext<T> {
644    /// Add context to an error
645    fn context(self, msg: &str) -> Result<T, P2PError>;
646
647    /// Add context with a closure
648    fn with_context<F>(self, f: F) -> Result<T, P2PError>
649    where
650        F: FnOnce() -> String;
651}
652
653impl<T, E> ErrorContext<T> for Result<T, E>
654where
655    E: Into<P2PError>,
656{
657    fn context(self, msg: &str) -> Result<T, P2PError> {
658        self.map_err(|e| {
659            let base_error = e.into();
660            P2PError::Internal(format!("{}: {}", msg, base_error).into())
661        })
662    }
663
664    fn with_context<F>(self, f: F) -> Result<T, P2PError>
665    where
666        F: FnOnce() -> String,
667    {
668        self.map_err(|e| {
669            let base_error = e.into();
670            P2PError::Internal(format!("{}: {}", f(), base_error).into())
671        })
672    }
673}
674
675/// Helper functions for error creation
676impl P2PError {
677    /// Create a network connection error
678    pub fn connection_failed(addr: SocketAddr, reason: impl Into<String>) -> Self {
679        P2PError::Network(NetworkError::ConnectionFailed {
680            addr,
681            reason: reason.into().into(),
682        })
683    }
684
685    /// Create a timeout error
686    pub fn timeout(duration: Duration) -> Self {
687        P2PError::Timeout(duration)
688    }
689
690    /// Create a validation error
691    pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
692        P2PError::Validation(msg.into())
693    }
694
695    /// Create an internal error
696    pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
697        P2PError::Internal(msg.into())
698    }
699}
700
701/// Logging integration for errors
702impl P2PError {
703    /// Log error with appropriate level
704    pub fn log(&self) {
705        use tracing::{error, warn};
706
707        match self {
708            P2PError::Network(NetworkError::Timeout) | P2PError::Timeout(_) => warn!("{}", self),
709
710            P2PError::Validation(_) | P2PError::Config(_) => warn!("{}", self),
711
712            _ => error!("{}", self),
713        }
714    }
715
716    /// Log error with context
717    pub fn log_with_context(&self, context: &str) {
718        use tracing::error;
719        error!("{}: {}", context, self);
720    }
721}
722
723// ===== Conversion implementations =====
724
725impl From<serde_json::Error> for P2PError {
726    fn from(err: serde_json::Error) -> Self {
727        P2PError::Serialization(err.to_string().into())
728    }
729}
730
731impl From<bincode::Error> for P2PError {
732    fn from(err: bincode::Error) -> Self {
733        P2PError::Serialization(err.to_string().into())
734    }
735}
736
737impl From<std::net::AddrParseError> for P2PError {
738    fn from(err: std::net::AddrParseError) -> Self {
739        P2PError::Network(NetworkError::InvalidAddress(err.to_string().into()))
740    }
741}
742
743impl From<tokio::time::error::Elapsed> for P2PError {
744    fn from(_: tokio::time::error::Elapsed) -> Self {
745        P2PError::Network(NetworkError::Timeout)
746    }
747}
748
749impl From<crate::adaptive::AdaptiveNetworkError> for P2PError {
750    fn from(err: crate::adaptive::AdaptiveNetworkError) -> Self {
751        use crate::adaptive::AdaptiveNetworkError;
752        match err {
753            AdaptiveNetworkError::Network(io_err) => P2PError::Io(io_err),
754            AdaptiveNetworkError::Io(io_err) => P2PError::Io(io_err),
755            AdaptiveNetworkError::Serialization(ser_err) => {
756                P2PError::Serialization(ser_err.to_string().into())
757            }
758            AdaptiveNetworkError::Routing(msg) => {
759                P2PError::Internal(format!("Routing error: {}", msg).into())
760            }
761            AdaptiveNetworkError::Trust(msg) => {
762                P2PError::Internal(format!("Trust error: {}", msg).into())
763            }
764            AdaptiveNetworkError::Learning(msg) => {
765                P2PError::Internal(format!("Learning error: {}", msg).into())
766            }
767            AdaptiveNetworkError::Gossip(msg) => {
768                P2PError::Internal(format!("Gossip error: {}", msg).into())
769            }
770            AdaptiveNetworkError::Other(msg) => P2PError::Internal(msg.into()),
771        }
772    }
773}
774
775// ===== Structured logging =====
776
777/// Value types for error context
778#[derive(Debug, Clone, Serialize, Deserialize)]
779pub enum ErrorValue {
780    String(Cow<'static, str>),
781    Number(i64),
782    Bool(bool),
783    Duration(Duration),
784    Address(SocketAddr),
785}
786
787/// Structured error log entry optimized for performance
788#[derive(Debug, Serialize, Deserialize)]
789pub struct ErrorLog {
790    pub timestamp: i64, // Unix timestamp for efficiency
791    pub error_type: &'static str,
792    pub message: Cow<'static, str>,
793    pub context: SmallVec<[(&'static str, ErrorValue); 4]>, // Stack-allocated for common cases
794    pub stack_trace: Option<Cow<'static, str>>,
795}
796
797impl ErrorLog {
798    /// Creates an error log entry from a P2PError
799    pub fn from_error(error: &P2PError) -> Self {
800        let mut context = SmallVec::new();
801
802        // Add error-specific context
803        match error {
804            P2PError::Network(NetworkError::ConnectionFailed { addr, reason }) => {
805                context.push(("address", ErrorValue::Address(*addr)));
806                context.push(("reason", ErrorValue::String(reason.clone())));
807            }
808            P2PError::Timeout(duration) => {
809                context.push(("timeout", ErrorValue::Duration(*duration)));
810            }
811            P2PError::Crypto(CryptoError::InvalidKeyLength { expected, actual }) => {
812                context.push(("expected_length", ErrorValue::Number(*expected as i64)));
813                context.push(("actual_length", ErrorValue::Number(*actual as i64)));
814            }
815            _ => {}
816        }
817
818        ErrorLog {
819            timestamp: chrono::Utc::now().timestamp(),
820            error_type: error_type_name(error),
821            message: error.to_string().into(),
822            context,
823            stack_trace: None,
824        }
825    }
826
827    pub fn with_context(mut self, key: &'static str, value: ErrorValue) -> Self {
828        self.context.push((key, value));
829        self
830    }
831
832    pub fn log(&self) {
833        use log::{error, warn};
834
835        let json = serde_json::to_string(self).unwrap_or_else(|_| self.message.to_string());
836
837        match self.error_type {
838            "Validation" | "Config" => warn!("{}", json),
839            _ => error!("{}", json),
840        }
841    }
842}
843
844fn error_type_name(error: &P2PError) -> &'static str {
845    match error {
846        P2PError::Network(_) => "Network",
847        P2PError::Dht(_) => "DHT",
848        P2PError::Identity(_) => "Identity",
849        P2PError::Crypto(_) => "Crypto",
850        P2PError::Storage(_) => "Storage",
851        P2PError::Transport(_) => "Transport",
852        P2PError::Config(_) => "Config",
853        P2PError::Io(_) => "IO",
854        P2PError::Serialization(_) => "Serialization",
855        P2PError::Validation(_) => "Validation",
856        P2PError::Timeout(_) => "Timeout",
857        P2PError::ResourceExhausted(_) => "ResourceExhausted",
858        P2PError::Internal(_) => "Internal",
859        P2PError::Security(_) => "Security",
860        P2PError::Bootstrap(_) => "Bootstrap",
861        P2PError::Encoding(_) => "Encoding",
862        P2PError::RecordTooLarge(_) => "RecordTooLarge",
863        P2PError::TimeError => "TimeError",
864        P2PError::InvalidInput(_) => "InvalidInput",
865        P2PError::WebRtcError(_) => "WebRTC",
866    }
867}
868
869/// Error reporting trait for structured logging
870pub trait ErrorReporting {
871    fn report(&self) -> ErrorLog;
872    fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog;
873}
874
875impl ErrorReporting for P2PError {
876    fn report(&self) -> ErrorLog {
877        ErrorLog::from_error(self)
878    }
879
880    fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog {
881        let log = ErrorLog::from_error(self);
882        // Convert HashMap entries to ErrorValue entries
883        for (_key, _value) in context {
884            // We need to leak the key to get a &'static str, or use a different approach
885            // For now, we'll skip this functionality as it requires a redesign
886            // log.context.push((key.leak(), ErrorValue::String(value.to_string().into())));
887        }
888        log
889    }
890}
891
892// ===== Anyhow integration =====
893
894/// Conversion helpers for anyhow integration
895pub trait IntoAnyhow<T> {
896    fn into_anyhow(self) -> anyhow::Result<T>;
897}
898
899impl<T> IntoAnyhow<T> for P2pResult<T> {
900    fn into_anyhow(self) -> anyhow::Result<T> {
901        self.map_err(|e| anyhow::anyhow!(e))
902    }
903}
904
905pub trait FromAnyhowExt<T> {
906    fn into_p2p_result(self) -> P2pResult<T>;
907}
908
909impl<T> FromAnyhowExt<T> for anyhow::Result<T> {
910    fn into_p2p_result(self) -> P2pResult<T> {
911        self.map_err(|e| P2PError::Internal(e.to_string().into()))
912    }
913}
914
915/// Re-export for convenience
916pub use anyhow::{Context as AnyhowContext, Result as AnyhowResult};
917
918#[cfg(test)]
919mod tests {
920    use super::*;
921
922    #[test]
923    fn test_error_display() {
924        let err =
925            P2PError::connection_failed("127.0.0.1:8080".parse().unwrap(), "Connection refused");
926        assert_eq!(
927            err.to_string(),
928            "Network error: Connection failed to 127.0.0.1:8080: Connection refused"
929        );
930    }
931
932    #[test]
933    fn test_error_context() {
934        let result: Result<(), io::Error> =
935            Err(io::Error::new(io::ErrorKind::NotFound, "file not found"));
936
937        let with_context = crate::error::ErrorContext::context(result, "Failed to load config");
938        assert!(with_context.is_err());
939        assert!(
940            with_context
941                .unwrap_err()
942                .to_string()
943                .contains("Failed to load config")
944        );
945    }
946
947    #[test]
948    fn test_timeout_error() {
949        let err = P2PError::timeout(Duration::from_secs(30));
950        assert_eq!(err.to_string(), "Operation timed out after 30s");
951    }
952
953    #[test]
954    fn test_crypto_error() {
955        let err = P2PError::Crypto(CryptoError::InvalidKeyLength {
956            expected: 32,
957            actual: 16,
958        });
959        assert_eq!(
960            err.to_string(),
961            "Cryptography error: Invalid key length: expected 32, got 16"
962        );
963    }
964
965    #[test]
966    fn test_error_log_serialization() {
967        let error = P2PError::Network(NetworkError::ConnectionFailed {
968            addr: "127.0.0.1:8080".parse().unwrap(),
969            reason: "Connection refused".into(),
970        });
971
972        let log = error
973            .report()
974            .with_context("peer_id", ErrorValue::String("peer123".into()))
975            .with_context("retry_count", ErrorValue::Number(3));
976
977        let json = serde_json::to_string_pretty(&log).unwrap();
978        assert!(json.contains("Network"));
979        assert!(json.contains("127.0.0.1:8080"));
980        assert!(json.contains("peer123"));
981    }
982
983    #[test]
984    fn test_anyhow_conversion() {
985        let p2p_result: P2pResult<()> = Err(P2PError::validation("Invalid input"));
986        let anyhow_result = p2p_result.into_anyhow();
987        assert!(anyhow_result.is_err());
988
989        let anyhow_err = anyhow::anyhow!("Test error");
990        let anyhow_result: anyhow::Result<()> = Err(anyhow_err);
991        let p2p_result = crate::error::FromAnyhowExt::into_p2p_result(anyhow_result);
992        assert!(p2p_result.is_err());
993        match p2p_result.unwrap_err() {
994            P2PError::Internal(msg) => assert!(msg.contains("Test error")),
995            _ => panic!("Expected Internal error"),
996        }
997    }
998}