1use 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#[derive(Debug, Error)]
104pub enum P2PError {
105 #[error("Network error: {0}")]
107 Network(#[from] NetworkError),
108
109 #[error("DHT error: {0}")]
111 Dht(#[from] DhtError),
112
113 #[error("Identity error: {0}")]
115 Identity(#[from] IdentityError),
116
117 #[error("Cryptography error: {0}")]
119 Crypto(#[from] CryptoError),
120
121 #[error("Storage error: {0}")]
123 Storage(#[from] StorageError),
124
125 #[error("Transport error: {0}")]
127 Transport(#[from] TransportError),
128
129 #[error("Configuration error: {0}")]
131 Config(#[from] ConfigError),
132
133 #[error("Security error: {0}")]
135 Security(#[from] SecurityError),
136
137 #[error("Bootstrap error: {0}")]
139 Bootstrap(#[from] BootstrapError),
140
141 #[error("IO error: {0}")]
143 Io(#[from] io::Error),
144
145 #[error("Serialization error: {0}")]
147 Serialization(Cow<'static, str>),
148
149 #[error("Validation error: {0}")]
151 Validation(Cow<'static, str>),
152
153 #[error("Operation timed out after {0:?}")]
155 Timeout(Duration),
156
157 #[error("Resource exhausted: {0}")]
159 ResourceExhausted(Cow<'static, str>),
160
161 #[error("Internal error: {0}")]
163 Internal(Cow<'static, str>),
164
165 #[error("Encoding error: {0}")]
167 Encoding(Cow<'static, str>),
168
169 #[error("Record too large: {0} bytes (max 512)")]
171 RecordTooLarge(usize),
172
173 #[error("Time error")]
175 TimeError,
176
177 #[error("Invalid input: {0}")]
179 InvalidInput(String),
180
181 #[error("WebRTC error: {0}")]
183 WebRtcError(String),
184
185 #[error("Trust error: {0}")]
187 Trust(Cow<'static, str>),
188}
189
190#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
521pub enum GeoEnforcementMode {
522 LogOnly,
524 #[default]
526 Strict,
527}
528
529#[derive(Debug, Clone)]
531pub struct GeographicConfig {
532 pub min_regions: usize,
534 pub max_single_region_ratio: f64,
536 pub blocked_regions: Vec<String>,
538 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 pub fn strict() -> Self {
556 Self::default()
557 }
558
559 pub fn log_only() -> Self {
561 Self {
562 enforcement_mode: GeoEnforcementMode::LogOnly,
563 ..Default::default()
564 }
565 }
566
567 pub fn with_blocked_region(mut self, region: impl Into<String>) -> Self {
569 self.blocked_regions.push(region.into());
570 self
571 }
572
573 pub fn with_max_ratio(mut self, ratio: f64) -> Self {
575 self.max_single_region_ratio = ratio;
576 self
577 }
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
596pub enum PeerFailureReason {
597 Timeout,
599 ConnectionFailed,
601 DataUnavailable,
603 CorruptedData,
605 ProtocolError,
607 Refused,
609}
610
611impl PeerFailureReason {
612 pub fn is_transient(&self) -> bool {
617 matches!(
618 self,
619 PeerFailureReason::Timeout | PeerFailureReason::ConnectionFailed
620 )
621 }
622
623 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
654pub type P2pResult<T> = Result<T, P2PError>;
656
657pub trait Recoverable {
661 fn is_transient(&self) -> bool;
663
664 fn suggested_retry_after(&self) -> Option<Duration>;
666
667 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
711pub trait ErrorContext<T> {
713 fn context(self, msg: &str) -> Result<T, P2PError>;
715
716 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
744impl P2PError {
746 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 pub fn timeout(duration: Duration) -> Self {
756 P2PError::Timeout(duration)
757 }
758
759 pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
761 P2PError::Validation(msg.into())
762 }
763
764 pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
766 P2PError::Internal(msg.into())
767 }
768}
769
770impl P2PError {
772 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 pub fn log_with_context(&self, context: &str) {
787 use tracing::error;
788 error!("{}: {}", context, self);
789 }
790}
791
792impl 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#[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#[derive(Debug, Serialize, Deserialize)]
857pub struct ErrorLog {
858 pub timestamp: i64, pub error_type: &'static str,
860 pub message: Cow<'static, str>,
861 pub context: SmallVec<[(&'static str, ErrorValue); 4]>, pub stack_trace: Option<Cow<'static, str>>,
863}
864
865impl ErrorLog {
866 pub fn from_error(error: &P2PError) -> Self {
868 let mut context = SmallVec::new();
869
870 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
938pub 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 for (_key, _value) in context {
953 }
957 log
958 }
959}
960
961pub 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
984pub 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 #[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}