1use 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#[derive(Debug, Error)]
113pub enum P2PError {
114 #[error("Network error: {0}")]
116 Network(#[from] NetworkError),
117
118 #[error("DHT error: {0}")]
120 Dht(#[from] DhtError),
121
122 #[error("Identity error: {0}")]
124 Identity(#[from] IdentityError),
125
126 #[error("Cryptography error: {0}")]
128 Crypto(#[from] CryptoError),
129
130 #[error("Storage error: {0}")]
132 Storage(#[from] StorageError),
133
134 #[error("Transport error: {0}")]
136 Transport(#[from] TransportError),
137
138 #[error("Configuration error: {0}")]
140 Config(#[from] ConfigError),
141
142 #[error("Security error: {0}")]
144 Security(#[from] SecurityError),
145
146 #[error("Bootstrap error: {0}")]
148 Bootstrap(#[from] BootstrapError),
149
150 #[error("IO error: {0}")]
152 Io(#[from] io::Error),
153
154 #[error("Serialization error: {0}")]
156 Serialization(Cow<'static, str>),
157
158 #[error("Validation error: {0}")]
160 Validation(Cow<'static, str>),
161
162 #[error("Operation timed out after {0:?}")]
164 Timeout(Duration),
165
166 #[error("Resource exhausted: {0}")]
168 ResourceExhausted(Cow<'static, str>),
169
170 #[error("Internal error: {0}")]
172 Internal(Cow<'static, str>),
173
174 #[error("Encoding error: {0}")]
176 Encoding(Cow<'static, str>),
177
178 #[error("Record too large: {0} bytes (max 512)")]
180 RecordTooLarge(usize),
181
182 #[error("Time error")]
184 TimeError,
185
186 #[error("Invalid input: {0}")]
188 InvalidInput(String),
189
190 #[error("WebRTC error: {0}")]
192 WebRtcError(String),
193}
194
195#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
526pub enum GeoEnforcementMode {
527 LogOnly,
529 #[default]
531 Strict,
532}
533
534#[derive(Debug, Clone)]
536pub struct GeographicConfig {
537 pub min_regions: usize,
539 pub max_single_region_ratio: f64,
541 pub blocked_regions: Vec<String>,
543 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 pub fn strict() -> Self {
561 Self::default()
562 }
563
564 pub fn log_only() -> Self {
566 Self {
567 enforcement_mode: GeoEnforcementMode::LogOnly,
568 ..Default::default()
569 }
570 }
571
572 pub fn with_blocked_region(mut self, region: impl Into<String>) -> Self {
574 self.blocked_regions.push(region.into());
575 self
576 }
577
578 pub fn with_max_ratio(mut self, ratio: f64) -> Self {
580 self.max_single_region_ratio = ratio;
581 self
582 }
583}
584
585pub type P2pResult<T> = Result<T, P2PError>;
587
588pub trait Recoverable {
592 fn is_transient(&self) -> bool;
594
595 fn suggested_retry_after(&self) -> Option<Duration>;
597
598 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
642pub trait ErrorContext<T> {
644 fn context(self, msg: &str) -> Result<T, P2PError>;
646
647 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
675impl P2PError {
677 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 pub fn timeout(duration: Duration) -> Self {
687 P2PError::Timeout(duration)
688 }
689
690 pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
692 P2PError::Validation(msg.into())
693 }
694
695 pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
697 P2PError::Internal(msg.into())
698 }
699}
700
701impl P2PError {
703 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 pub fn log_with_context(&self, context: &str) {
718 use tracing::error;
719 error!("{}: {}", context, self);
720 }
721}
722
723impl 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#[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#[derive(Debug, Serialize, Deserialize)]
789pub struct ErrorLog {
790 pub timestamp: i64, pub error_type: &'static str,
792 pub message: Cow<'static, str>,
793 pub context: SmallVec<[(&'static str, ErrorValue); 4]>, pub stack_trace: Option<Cow<'static, str>>,
795}
796
797impl ErrorLog {
798 pub fn from_error(error: &P2PError) -> Self {
800 let mut context = SmallVec::new();
801
802 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
869pub 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 for (_key, _value) in context {
884 }
888 log
889 }
890}
891
892pub 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
915pub 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}