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
186#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
517pub enum GeoEnforcementMode {
518 LogOnly,
520 #[default]
522 Strict,
523}
524
525#[derive(Debug, Clone)]
527pub struct GeographicConfig {
528 pub min_regions: usize,
530 pub max_single_region_ratio: f64,
532 pub blocked_regions: Vec<String>,
534 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 pub fn strict() -> Self {
552 Self::default()
553 }
554
555 pub fn log_only() -> Self {
557 Self {
558 enforcement_mode: GeoEnforcementMode::LogOnly,
559 ..Default::default()
560 }
561 }
562
563 pub fn with_blocked_region(mut self, region: impl Into<String>) -> Self {
565 self.blocked_regions.push(region.into());
566 self
567 }
568
569 pub fn with_max_ratio(mut self, ratio: f64) -> Self {
571 self.max_single_region_ratio = ratio;
572 self
573 }
574}
575
576pub type P2pResult<T> = Result<T, P2PError>;
578
579pub trait Recoverable {
583 fn is_transient(&self) -> bool;
585
586 fn suggested_retry_after(&self) -> Option<Duration>;
588
589 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
633pub trait ErrorContext<T> {
635 fn context(self, msg: &str) -> Result<T, P2PError>;
637
638 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
666impl P2PError {
668 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 pub fn timeout(duration: Duration) -> Self {
678 P2PError::Timeout(duration)
679 }
680
681 pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
683 P2PError::Validation(msg.into())
684 }
685
686 pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
688 P2PError::Internal(msg.into())
689 }
690}
691
692impl P2PError {
694 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 pub fn log_with_context(&self, context: &str) {
709 use tracing::error;
710 error!("{}: {}", context, self);
711 }
712}
713
714impl 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#[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#[derive(Debug, Serialize, Deserialize)]
779pub struct ErrorLog {
780 pub timestamp: i64, pub error_type: &'static str,
782 pub message: Cow<'static, str>,
783 pub context: SmallVec<[(&'static str, ErrorValue); 4]>, pub stack_trace: Option<Cow<'static, str>>,
785}
786
787impl ErrorLog {
788 pub fn from_error(error: &P2PError) -> Self {
790 let mut context = SmallVec::new();
791
792 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
859pub 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 for (_key, _value) in context {
874 }
878 log
879 }
880}
881
882pub 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
905pub 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}