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    // Trust system errors
186    #[error("Trust error: {0}")]
187    Trust(Cow<'static, str>),
188}
189
190/// Network-related errors
191#[derive(Debug, Error)]
192pub enum NetworkError {
193    #[error("Connection failed to {addr}: {reason}")]
194    ConnectionFailed {
195        addr: SocketAddr,
196        reason: Cow<'static, str>,
197    },
198
199    #[error("Connection closed unexpectedly for peer: {peer_id}")]
200    ConnectionClosed { peer_id: Cow<'static, str> },
201
202    #[error("Invalid network address: {0}")]
203    InvalidAddress(Cow<'static, str>),
204
205    #[error("Peer not found: {0}")]
206    PeerNotFound(Cow<'static, str>),
207
208    #[error("Peer disconnected - peer: {peer}, reason: {reason}")]
209    PeerDisconnected { peer: String, reason: String },
210
211    #[error("Network timeout")]
212    Timeout,
213
214    #[error("Too many connections")]
215    TooManyConnections,
216
217    #[error("Protocol error: {0}")]
218    ProtocolError(Cow<'static, str>),
219
220    #[error("Bind error: {0}")]
221    BindError(Cow<'static, str>),
222}
223
224/// DHT-related errors
225#[derive(Debug, Error)]
226pub enum DhtError {
227    #[error("Key not found: {0}")]
228    KeyNotFound(Cow<'static, str>),
229
230    #[error("Store operation failed: {0}")]
231    StoreFailed(Cow<'static, str>),
232
233    #[error("Invalid key format: {0}")]
234    InvalidKey(Cow<'static, str>),
235
236    #[error("Routing table full")]
237    RoutingTableFull,
238
239    #[error("No suitable peers found")]
240    NoPeersFound,
241
242    #[error("Replication failed: {0}")]
243    ReplicationFailed(Cow<'static, str>),
244
245    #[error("Query timeout")]
246    QueryTimeout,
247
248    #[error("Routing error: {0}")]
249    RoutingError(Cow<'static, str>),
250
251    #[error("Storage failed: {0}")]
252    StorageFailed(Cow<'static, str>),
253
254    #[error("Insufficient replicas: {0}")]
255    InsufficientReplicas(Cow<'static, str>),
256}
257
258/// Identity-related errors
259#[derive(Debug, Error)]
260pub enum IdentityError {
261    #[error("Invalid three-word address: {0}")]
262    InvalidThreeWordAddress(Cow<'static, str>),
263
264    #[error("Invalid four-word address: {0}")]
265    InvalidFourWordAddress(Cow<'static, str>),
266
267    #[error("Identity not found: {0}")]
268    IdentityNotFound(Cow<'static, str>),
269
270    #[error("Identity already exists: {0}")]
271    IdentityExists(Cow<'static, str>),
272
273    #[error("Invalid signature")]
274    InvalidSignature,
275
276    #[error("Invalid canonical bytes")]
277    InvalidCanonicalBytes,
278
279    #[error("Membership conflict")]
280    MembershipConflict,
281
282    #[error("Missing group key")]
283    MissingGroupKey,
284
285    #[error("Website root update refused")]
286    WebsiteRootUpdateRefused,
287
288    #[error("Key derivation failed: {0}")]
289    KeyDerivationFailed(Cow<'static, str>),
290
291    #[error("Permission denied")]
292    PermissionDenied,
293
294    #[error("Invalid peer ID: {0}")]
295    InvalidPeerId(Cow<'static, str>),
296
297    #[error("Invalid format: {0}")]
298    InvalidFormat(Cow<'static, str>),
299
300    #[error("System time error: {0}")]
301    SystemTime(Cow<'static, str>),
302
303    #[error("Not found: {0}")]
304    NotFound(Cow<'static, str>),
305
306    #[error("Verification failed: {0}")]
307    VerificationFailed(Cow<'static, str>),
308
309    #[error("Insufficient entropy")]
310    InsufficientEntropy,
311
312    #[error("Access denied: {0}")]
313    AccessDenied(Cow<'static, str>),
314}
315
316/// Cryptography-related errors
317#[derive(Debug, Error)]
318pub enum CryptoError {
319    #[error("Encryption failed: {0}")]
320    EncryptionFailed(Cow<'static, str>),
321
322    #[error("Decryption failed: {0}")]
323    DecryptionFailed(Cow<'static, str>),
324
325    #[error("Invalid key length: expected {expected}, got {actual}")]
326    InvalidKeyLength { expected: usize, actual: usize },
327
328    #[error("Signature verification failed")]
329    SignatureVerificationFailed,
330
331    #[error("Key generation failed: {0}")]
332    KeyGenerationFailed(Cow<'static, str>),
333
334    #[error("Invalid public key")]
335    InvalidPublicKey,
336
337    #[error("Invalid private key")]
338    InvalidPrivateKey,
339
340    #[error("HKDF expansion failed: {0}")]
341    HkdfError(Cow<'static, str>),
342}
343
344/// Storage-related errors
345#[derive(Debug, Error)]
346pub enum StorageError {
347    #[error("Database error: {0}")]
348    Database(Cow<'static, str>),
349
350    #[error("Disk full")]
351    DiskFull,
352
353    #[error("Corrupt data: {0}")]
354    CorruptData(Cow<'static, str>),
355
356    #[error("Storage path not found: {0}")]
357    PathNotFound(Cow<'static, str>),
358
359    #[error("Permission denied: {0}")]
360    PermissionDenied(Cow<'static, str>),
361
362    #[error("Lock acquisition failed")]
363    LockFailed,
364
365    #[error("Lock poisoned: {0}")]
366    LockPoisoned(Cow<'static, str>),
367
368    #[error("File not found: {0}")]
369    FileNotFound(Cow<'static, str>),
370
371    #[error("Corruption detected: {0}")]
372    CorruptionDetected(Cow<'static, str>),
373}
374
375/// Transport-related errors
376#[derive(Debug, Error)]
377pub enum TransportError {
378    #[error("QUIC error: {0}")]
379    Quic(Cow<'static, str>),
380
381    #[error("TCP error: {0}")]
382    Tcp(Cow<'static, str>),
383
384    #[error("Invalid transport configuration: {0}")]
385    InvalidConfig(Cow<'static, str>),
386
387    #[error("Transport not supported: {0}")]
388    NotSupported(Cow<'static, str>),
389
390    #[error("Stream error: {0}")]
391    StreamError(Cow<'static, str>),
392
393    #[error("Certificate error: {0}")]
394    CertificateError(Cow<'static, str>),
395
396    #[error("Setup failed: {0}")]
397    SetupFailed(Cow<'static, str>),
398
399    #[error("Connection failed to {addr}: {reason}")]
400    ConnectionFailed {
401        addr: SocketAddr,
402        reason: Cow<'static, str>,
403    },
404
405    #[error("Bind error: {0}")]
406    BindError(Cow<'static, str>),
407
408    #[error("Accept failed: {0}")]
409    AcceptFailed(Cow<'static, str>),
410
411    #[error("Not listening")]
412    NotListening,
413
414    #[error("Not initialized")]
415    NotInitialized,
416}
417
418/// Configuration-related errors
419#[derive(Debug, Error)]
420pub enum ConfigError {
421    #[error("Missing required field: {0}")]
422    MissingField(Cow<'static, str>),
423
424    #[error("Invalid value for {field}: {reason}")]
425    InvalidValue {
426        field: Cow<'static, str>,
427        reason: Cow<'static, str>,
428    },
429
430    #[error("Configuration file not found: {0}")]
431    FileNotFound(Cow<'static, str>),
432
433    #[error("Parse error: {0}")]
434    ParseError(Cow<'static, str>),
435
436    #[error("Validation failed: {0}")]
437    ValidationFailed(Cow<'static, str>),
438
439    #[error("IO error for {path}: {source}")]
440    IoError {
441        path: Cow<'static, str>,
442        #[source]
443        source: std::io::Error,
444    },
445}
446
447/// Security-related errors
448#[derive(Debug, Error)]
449pub enum SecurityError {
450    #[error("Authentication failed")]
451    AuthenticationFailed,
452
453    #[error("Authorization denied")]
454    AuthorizationDenied,
455
456    #[error("Invalid credentials")]
457    InvalidCredentials,
458
459    #[error("Certificate error: {0}")]
460    CertificateError(Cow<'static, str>),
461
462    #[error("Encryption failed: {0}")]
463    EncryptionFailed(Cow<'static, str>),
464
465    #[error("Decryption failed: {0}")]
466    DecryptionFailed(Cow<'static, str>),
467
468    #[error("Invalid key: {0}")]
469    InvalidKey(Cow<'static, str>),
470
471    #[error("Signature verification failed: {0}")]
472    SignatureVerificationFailed(Cow<'static, str>),
473
474    #[error("Key generation failed: {0}")]
475    KeyGenerationFailed(Cow<'static, str>),
476
477    #[error("Authorization failed: {0}")]
478    AuthorizationFailed(Cow<'static, str>),
479}
480
481/// Bootstrap-related errors
482#[derive(Debug, Error)]
483pub enum BootstrapError {
484    #[error("No bootstrap nodes available")]
485    NoBootstrapNodes,
486
487    #[error("Bootstrap failed: {0}")]
488    BootstrapFailed(Cow<'static, str>),
489
490    #[error("Invalid bootstrap node: {0}")]
491    InvalidBootstrapNode(Cow<'static, str>),
492
493    #[error("Bootstrap timeout")]
494    BootstrapTimeout,
495
496    #[error("Cache error: {0}")]
497    CacheError(Cow<'static, str>),
498
499    #[error("Invalid data: {0}")]
500    InvalidData(Cow<'static, str>),
501
502    #[error("Rate limited: {0}")]
503    RateLimited(Cow<'static, str>),
504}
505
506/// Geographic validation errors for connection rejection
507#[derive(Debug, Error, Clone)]
508pub enum GeoRejectionError {
509    #[error("Peer from blocked region: {0}")]
510    BlockedRegion(String),
511
512    #[error("Geographic diversity violation in region {region} (ratio: {current_ratio:.1}%)")]
513    DiversityViolation { region: String, current_ratio: f64 },
514
515    #[error("Region lookup failed: {0}")]
516    LookupFailed(String),
517}
518
519/// Geographic enforcement mode
520#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
521pub enum GeoEnforcementMode {
522    /// Audit mode - log violations but allow connections
523    LogOnly,
524    /// Strict mode - reject connections that violate rules
525    #[default]
526    Strict,
527}
528
529/// Configuration for geographic diversity enforcement
530#[derive(Debug, Clone)]
531pub struct GeographicConfig {
532    /// Minimum number of regions required (default: 3)
533    pub min_regions: usize,
534    /// Maximum ratio of peers from a single region (default: 0.4 = 40%)
535    pub max_single_region_ratio: f64,
536    /// Regions to outright block
537    pub blocked_regions: Vec<String>,
538    /// Enforcement mode (LogOnly or Strict)
539    pub enforcement_mode: GeoEnforcementMode,
540}
541
542impl Default for GeographicConfig {
543    fn default() -> Self {
544        Self {
545            min_regions: 3,
546            max_single_region_ratio: 0.4,
547            blocked_regions: Vec::new(),
548            enforcement_mode: GeoEnforcementMode::Strict,
549        }
550    }
551}
552
553impl GeographicConfig {
554    /// Create a new config with strict enforcement (default)
555    pub fn strict() -> Self {
556        Self::default()
557    }
558
559    /// Create a config for logging only (no rejection)
560    pub fn log_only() -> Self {
561        Self {
562            enforcement_mode: GeoEnforcementMode::LogOnly,
563            ..Default::default()
564        }
565    }
566
567    /// Add a blocked region
568    pub fn with_blocked_region(mut self, region: impl Into<String>) -> Self {
569        self.blocked_regions.push(region.into());
570        self
571    }
572
573    /// Set maximum single region ratio
574    pub fn with_max_ratio(mut self, ratio: f64) -> Self {
575        self.max_single_region_ratio = ratio;
576        self
577    }
578}
579
580/// Categorizes why a peer interaction failed.
581///
582/// Used by consumers (like saorsa-node) to provide rich context when reporting
583/// failures to the trust/reputation system. Each variant carries a severity
584/// that the trust engine uses to weight the penalty.
585///
586/// # Example
587///
588/// ```rust,ignore
589/// use saorsa_core::error::PeerFailureReason;
590///
591/// let reason = PeerFailureReason::Timeout;
592/// assert!(reason.is_transient());
593/// assert!(reason.trust_severity() < 0.5);
594/// ```
595#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
596pub enum PeerFailureReason {
597    /// The peer did not respond within the expected time window.
598    Timeout,
599    /// Could not establish or maintain a connection to the peer.
600    ConnectionFailed,
601    /// The peer was reachable but did not have the requested data.
602    DataUnavailable,
603    /// The peer returned data that failed integrity/hash verification.
604    CorruptedData,
605    /// The peer violated the expected wire protocol.
606    ProtocolError,
607    /// The peer explicitly refused the request.
608    Refused,
609}
610
611impl PeerFailureReason {
612    /// Whether this failure is transient (likely to succeed on retry).
613    ///
614    /// Transient failures (timeout, connection issues) should not be penalized
615    /// as heavily as persistent ones (corrupted data, protocol errors).
616    pub fn is_transient(&self) -> bool {
617        matches!(
618            self,
619            PeerFailureReason::Timeout | PeerFailureReason::ConnectionFailed
620        )
621    }
622
623    /// Trust severity score in the range `[0.0, 1.0]`.
624    ///
625    /// Higher values indicate more severe trust violations:
626    /// - `0.2` — transient issues (timeout, connection)
627    /// - `0.5` — data unavailable / refused
628    /// - `1.0` — data corruption or protocol violation
629    pub fn trust_severity(&self) -> f64 {
630        match self {
631            PeerFailureReason::Timeout => 0.2,
632            PeerFailureReason::ConnectionFailed => 0.2,
633            PeerFailureReason::DataUnavailable => 0.5,
634            PeerFailureReason::CorruptedData => 1.0,
635            PeerFailureReason::ProtocolError => 1.0,
636            PeerFailureReason::Refused => 0.5,
637        }
638    }
639}
640
641impl std::fmt::Display for PeerFailureReason {
642    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643        match self {
644            PeerFailureReason::Timeout => write!(f, "timeout"),
645            PeerFailureReason::ConnectionFailed => write!(f, "connection_failed"),
646            PeerFailureReason::DataUnavailable => write!(f, "data_unavailable"),
647            PeerFailureReason::CorruptedData => write!(f, "corrupted_data"),
648            PeerFailureReason::ProtocolError => write!(f, "protocol_error"),
649            PeerFailureReason::Refused => write!(f, "refused"),
650        }
651    }
652}
653
654/// Result type alias for P2P operations
655pub type P2pResult<T> = Result<T, P2PError>;
656
657// ===== Recovery patterns =====
658
659/// Trait for errors that can be recovered from with retry
660pub trait Recoverable {
661    /// Check if this error is transient and can be retried
662    fn is_transient(&self) -> bool;
663
664    /// Suggested delay before retry
665    fn suggested_retry_after(&self) -> Option<Duration>;
666
667    /// Maximum number of retries recommended
668    fn max_retries(&self) -> usize;
669}
670
671impl Recoverable for P2PError {
672    fn is_transient(&self) -> bool {
673        match self {
674            P2PError::Network(NetworkError::ConnectionFailed { .. }) => true,
675            P2PError::Network(NetworkError::Timeout) => true,
676            P2PError::Transport(TransportError::ConnectionFailed { .. }) => true,
677            P2PError::Dht(DhtError::QueryTimeout) => true,
678            P2PError::Timeout(_) => true,
679            P2PError::ResourceExhausted(_) => true,
680            P2PError::Io(err) => matches!(
681                err.kind(),
682                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut | io::ErrorKind::Interrupted
683            ),
684            _ => false,
685        }
686    }
687
688    fn suggested_retry_after(&self) -> Option<Duration> {
689        match self {
690            P2PError::Network(NetworkError::Timeout) => Some(Duration::from_secs(5)),
691            P2PError::Timeout(duration) => Some(*duration * 2),
692            P2PError::ResourceExhausted(_) => Some(Duration::from_secs(30)),
693            P2PError::Transport(TransportError::ConnectionFailed { .. }) => {
694                Some(Duration::from_secs(1))
695            }
696            _ => None,
697        }
698    }
699
700    fn max_retries(&self) -> usize {
701        match self {
702            P2PError::Network(NetworkError::ConnectionFailed { .. }) => 3,
703            P2PError::Transport(TransportError::ConnectionFailed { .. }) => 3,
704            P2PError::Timeout(_) => 2,
705            P2PError::ResourceExhausted(_) => 1,
706            _ => 0,
707        }
708    }
709}
710
711/// Extension trait for adding context to errors
712pub trait ErrorContext<T> {
713    /// Add context to an error
714    fn context(self, msg: &str) -> Result<T, P2PError>;
715
716    /// Add context with a closure
717    fn with_context<F>(self, f: F) -> Result<T, P2PError>
718    where
719        F: FnOnce() -> String;
720}
721
722impl<T, E> ErrorContext<T> for Result<T, E>
723where
724    E: Into<P2PError>,
725{
726    fn context(self, msg: &str) -> Result<T, P2PError> {
727        self.map_err(|e| {
728            let base_error = e.into();
729            P2PError::Internal(format!("{}: {}", msg, base_error).into())
730        })
731    }
732
733    fn with_context<F>(self, f: F) -> Result<T, P2PError>
734    where
735        F: FnOnce() -> String,
736    {
737        self.map_err(|e| {
738            let base_error = e.into();
739            P2PError::Internal(format!("{}: {}", f(), base_error).into())
740        })
741    }
742}
743
744/// Helper functions for error creation
745impl P2PError {
746    /// Create a network connection error
747    pub fn connection_failed(addr: SocketAddr, reason: impl Into<String>) -> Self {
748        P2PError::Network(NetworkError::ConnectionFailed {
749            addr,
750            reason: reason.into().into(),
751        })
752    }
753
754    /// Create a timeout error
755    pub fn timeout(duration: Duration) -> Self {
756        P2PError::Timeout(duration)
757    }
758
759    /// Create a validation error
760    pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
761        P2PError::Validation(msg.into())
762    }
763
764    /// Create an internal error
765    pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
766        P2PError::Internal(msg.into())
767    }
768}
769
770/// Logging integration for errors
771impl P2PError {
772    /// Log error with appropriate level
773    pub fn log(&self) {
774        use tracing::{error, warn};
775
776        match self {
777            P2PError::Network(NetworkError::Timeout) | P2PError::Timeout(_) => warn!("{}", self),
778
779            P2PError::Validation(_) | P2PError::Config(_) => warn!("{}", self),
780
781            _ => error!("{}", self),
782        }
783    }
784
785    /// Log error with context
786    pub fn log_with_context(&self, context: &str) {
787        use tracing::error;
788        error!("{}: {}", context, self);
789    }
790}
791
792// ===== Conversion implementations =====
793
794impl From<serde_json::Error> for P2PError {
795    fn from(err: serde_json::Error) -> Self {
796        P2PError::Serialization(err.to_string().into())
797    }
798}
799
800impl From<postcard::Error> for P2PError {
801    fn from(err: postcard::Error) -> Self {
802        P2PError::Serialization(err.to_string().into())
803    }
804}
805
806impl From<std::net::AddrParseError> for P2PError {
807    fn from(err: std::net::AddrParseError) -> Self {
808        P2PError::Network(NetworkError::InvalidAddress(err.to_string().into()))
809    }
810}
811
812impl From<tokio::time::error::Elapsed> for P2PError {
813    fn from(_: tokio::time::error::Elapsed) -> Self {
814        P2PError::Network(NetworkError::Timeout)
815    }
816}
817
818impl From<crate::adaptive::AdaptiveNetworkError> for P2PError {
819    fn from(err: crate::adaptive::AdaptiveNetworkError) -> Self {
820        use crate::adaptive::AdaptiveNetworkError;
821        match err {
822            AdaptiveNetworkError::Network(io_err) => P2PError::Io(io_err),
823            AdaptiveNetworkError::Serialization(ser_err) => {
824                P2PError::Serialization(ser_err.to_string().into())
825            }
826            AdaptiveNetworkError::Routing(msg) => {
827                P2PError::Internal(format!("Routing error: {msg}").into())
828            }
829            AdaptiveNetworkError::Trust(msg) => {
830                P2PError::Internal(format!("Trust error: {msg}").into())
831            }
832            AdaptiveNetworkError::Learning(msg) => {
833                P2PError::Internal(format!("Learning error: {msg}").into())
834            }
835            AdaptiveNetworkError::Gossip(msg) => {
836                P2PError::Internal(format!("Gossip error: {msg}").into())
837            }
838            AdaptiveNetworkError::Other(msg) => P2PError::Internal(msg.into()),
839        }
840    }
841}
842
843// ===== Structured logging =====
844
845/// Value types for error context
846#[derive(Debug, Clone, Serialize, Deserialize)]
847pub enum ErrorValue {
848    String(Cow<'static, str>),
849    Number(i64),
850    Bool(bool),
851    Duration(Duration),
852    Address(SocketAddr),
853}
854
855/// Structured error log entry optimized for performance
856#[derive(Debug, Serialize, Deserialize)]
857pub struct ErrorLog {
858    pub timestamp: i64, // Unix timestamp for efficiency
859    pub error_type: &'static str,
860    pub message: Cow<'static, str>,
861    pub context: SmallVec<[(&'static str, ErrorValue); 4]>, // Stack-allocated for common cases
862    pub stack_trace: Option<Cow<'static, str>>,
863}
864
865impl ErrorLog {
866    /// Creates an error log entry from a P2PError
867    pub fn from_error(error: &P2PError) -> Self {
868        let mut context = SmallVec::new();
869
870        // Add error-specific context
871        match error {
872            P2PError::Network(NetworkError::ConnectionFailed { addr, reason }) => {
873                context.push(("address", ErrorValue::Address(*addr)));
874                context.push(("reason", ErrorValue::String(reason.clone())));
875            }
876            P2PError::Timeout(duration) => {
877                context.push(("timeout", ErrorValue::Duration(*duration)));
878            }
879            P2PError::Crypto(CryptoError::InvalidKeyLength { expected, actual }) => {
880                context.push(("expected_length", ErrorValue::Number(*expected as i64)));
881                context.push(("actual_length", ErrorValue::Number(*actual as i64)));
882            }
883            _ => {}
884        }
885
886        ErrorLog {
887            timestamp: chrono::Utc::now().timestamp(),
888            error_type: error_type_name(error),
889            message: error.to_string().into(),
890            context,
891            stack_trace: None,
892        }
893    }
894
895    pub fn with_context(mut self, key: &'static str, value: ErrorValue) -> Self {
896        self.context.push((key, value));
897        self
898    }
899
900    pub fn log(&self) {
901        use log::{error, warn};
902
903        let json = serde_json::to_string(self).unwrap_or_else(|_| self.message.to_string());
904
905        match self.error_type {
906            "Validation" | "Config" => warn!("{}", json),
907            _ => error!("{}", json),
908        }
909    }
910}
911
912fn error_type_name(error: &P2PError) -> &'static str {
913    match error {
914        P2PError::Network(_) => "Network",
915        P2PError::Dht(_) => "DHT",
916        P2PError::Identity(_) => "Identity",
917        P2PError::Crypto(_) => "Crypto",
918        P2PError::Storage(_) => "Storage",
919        P2PError::Transport(_) => "Transport",
920        P2PError::Config(_) => "Config",
921        P2PError::Io(_) => "IO",
922        P2PError::Serialization(_) => "Serialization",
923        P2PError::Validation(_) => "Validation",
924        P2PError::Timeout(_) => "Timeout",
925        P2PError::ResourceExhausted(_) => "ResourceExhausted",
926        P2PError::Internal(_) => "Internal",
927        P2PError::Security(_) => "Security",
928        P2PError::Bootstrap(_) => "Bootstrap",
929        P2PError::Encoding(_) => "Encoding",
930        P2PError::RecordTooLarge(_) => "RecordTooLarge",
931        P2PError::TimeError => "TimeError",
932        P2PError::InvalidInput(_) => "InvalidInput",
933        P2PError::WebRtcError(_) => "WebRTC",
934        P2PError::Trust(_) => "Trust",
935    }
936}
937
938/// Error reporting trait for structured logging
939pub trait ErrorReporting {
940    fn report(&self) -> ErrorLog;
941    fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog;
942}
943
944impl ErrorReporting for P2PError {
945    fn report(&self) -> ErrorLog {
946        ErrorLog::from_error(self)
947    }
948
949    fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog {
950        let log = ErrorLog::from_error(self);
951        // Convert HashMap entries to ErrorValue entries
952        for (_key, _value) in context {
953            // We need to leak the key to get a &'static str, or use a different approach
954            // For now, we'll skip this functionality as it requires a redesign
955            // log.context.push((key.leak(), ErrorValue::String(value.to_string().into())));
956        }
957        log
958    }
959}
960
961// ===== Anyhow integration =====
962
963/// Conversion helpers for anyhow integration
964pub trait IntoAnyhow<T> {
965    fn into_anyhow(self) -> anyhow::Result<T>;
966}
967
968impl<T> IntoAnyhow<T> for P2pResult<T> {
969    fn into_anyhow(self) -> anyhow::Result<T> {
970        self.map_err(|e| anyhow::anyhow!(e))
971    }
972}
973
974pub trait FromAnyhowExt<T> {
975    fn into_p2p_result(self) -> P2pResult<T>;
976}
977
978impl<T> FromAnyhowExt<T> for anyhow::Result<T> {
979    fn into_p2p_result(self) -> P2pResult<T> {
980        self.map_err(|e| P2PError::Internal(e.to_string().into()))
981    }
982}
983
984/// Re-export for convenience
985pub use anyhow::{Context as AnyhowContext, Result as AnyhowResult};
986
987#[cfg(test)]
988mod tests {
989    use super::*;
990
991    #[test]
992    fn test_error_display() {
993        let err =
994            P2PError::connection_failed("127.0.0.1:8080".parse().unwrap(), "Connection refused");
995        assert_eq!(
996            err.to_string(),
997            "Network error: Connection failed to 127.0.0.1:8080: Connection refused"
998        );
999    }
1000
1001    #[test]
1002    fn test_error_context() {
1003        let result: Result<(), io::Error> =
1004            Err(io::Error::new(io::ErrorKind::NotFound, "file not found"));
1005
1006        let with_context = crate::error::ErrorContext::context(result, "Failed to load config");
1007        assert!(with_context.is_err());
1008        assert!(
1009            with_context
1010                .unwrap_err()
1011                .to_string()
1012                .contains("Failed to load config")
1013        );
1014    }
1015
1016    #[test]
1017    fn test_timeout_error() {
1018        let err = P2PError::timeout(Duration::from_secs(30));
1019        assert_eq!(err.to_string(), "Operation timed out after 30s");
1020    }
1021
1022    #[test]
1023    fn test_crypto_error() {
1024        let err = P2PError::Crypto(CryptoError::InvalidKeyLength {
1025            expected: 32,
1026            actual: 16,
1027        });
1028        assert_eq!(
1029            err.to_string(),
1030            "Cryptography error: Invalid key length: expected 32, got 16"
1031        );
1032    }
1033
1034    #[test]
1035    fn test_error_log_serialization() {
1036        let error = P2PError::Network(NetworkError::ConnectionFailed {
1037            addr: "127.0.0.1:8080".parse().unwrap(),
1038            reason: "Connection refused".into(),
1039        });
1040
1041        let log = error
1042            .report()
1043            .with_context("peer_id", ErrorValue::String("peer123".into()))
1044            .with_context("retry_count", ErrorValue::Number(3));
1045
1046        let json = serde_json::to_string_pretty(&log).unwrap();
1047        assert!(json.contains("Network"));
1048        assert!(json.contains("127.0.0.1:8080"));
1049        assert!(json.contains("peer123"));
1050    }
1051
1052    // PeerFailureReason transient/severity tests are in tests/request_response_trust_test.rs
1053
1054    #[test]
1055    fn test_anyhow_conversion() {
1056        let p2p_result: P2pResult<()> = Err(P2PError::validation("Invalid input"));
1057        let anyhow_result = p2p_result.into_anyhow();
1058        assert!(anyhow_result.is_err());
1059
1060        let anyhow_err = anyhow::anyhow!("Test error");
1061        let anyhow_result: anyhow::Result<()> = Err(anyhow_err);
1062        let p2p_result = crate::error::FromAnyhowExt::into_p2p_result(anyhow_result);
1063        assert!(p2p_result.is_err());
1064        match p2p_result.unwrap_err() {
1065            P2PError::Internal(msg) => assert!(msg.contains("Test error")),
1066            _ => panic!("Expected Internal error"),
1067        }
1068    }
1069}