Skip to main content

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