Skip to main content

kinetic_core/error/
network.rs

1//! libp2p P2P network client error types (`KIN-NET-NNN`).
2//!
3//! [`NetworkClientError`] is emitted by `KineticNetworkClient` operations. It transparently wraps
4//! libp2p Kademlia, GossipSub, or the internal mpsc command channel fails.
5//!
6//! ## Namespace Note
7//!
8//! `KIN-NET-NNN` is shared between this type and `KineticStoreError` in `kinetic-network`.
9//! The two ranges do not overlap:
10//! - `KIN-NET-001..009`: This type (client-side failures)
11//! - `KIN-NET-001..020`: `KineticStoreError` (store-layer rejections)
12//!
13//! The `KineticStoreError` codes take precedence in external-facing API responses
14//! because they carry richer rejection context. This type is used internally within
15//! the event loop for command dispatch failures.
16
17use super::Severity;
18use thiserror::Error;
19
20/// Errors originating from the Network Client (DHT, proxy, gossipsub)
21#[derive(Error, Debug, PartialEq, Eq)]
22pub enum NetworkClientError {
23    /// A DHT query or stream operation exceeded its deadline.
24    #[error("Request timed out")]
25    Timeout,
26    /// The local node has no reachable peers.
27    #[error("Node is offline or unreachable")]
28    Offline,
29    /// The Kademlia routing table contains no known peers.
30    #[error("Routing table is empty")]
31    RoutingTableEmpty,
32    /// The internal mpsc/oneshot channel between the caller and the network loop was closed.
33    #[error("Internal channel closed")]
34    ChannelClosed,
35    /// The remote peer closed the stream before the response was fully delivered.
36    #[error("Stream dropped by peer")]
37    StreamDropped,
38    /// The remote peer does not speak the requested Kinetic protocol version.
39    #[error("Unsupported protocol")]
40    UnsupportedProtocol,
41    /// A GossipSub publish or subscribe operation failed.
42    #[error("Gossipsub error: {0}")]
43    GossipSubError(String),
44    /// The Kademlia record store rejected a `PUT` or returned an error for a `GET`.
45    #[error("Kademlia store error: {0}")]
46    StoreError(String),
47    /// A catch-all for miscellaneous network errors.
48    #[error("Other network error: {0}")]
49    Other(String),
50}
51
52impl NetworkClientError {
53    /// Stable protocol error code. Part of the Kinetic error taxonomy.
54    pub fn code(&self) -> &'static str {
55        match self {
56            Self::Timeout => "KIN-NET-001",
57            Self::Offline => "KIN-NET-002",
58            Self::RoutingTableEmpty => "KIN-NET-003",
59            Self::ChannelClosed => "KIN-NET-004",
60            Self::StreamDropped => "KIN-NET-005",
61            Self::UnsupportedProtocol => "KIN-NET-006",
62            Self::GossipSubError(_) => "KIN-NET-007",
63            Self::StoreError(_) => "KIN-NET-008",
64            Self::Other(_) => "KIN-NET-009",
65        }
66    }
67
68    /// RFC 7807 type URI for this error.
69    pub fn error_type_uri(&self) -> String {
70        format!("{}/errors/{}", crate::constants::DOCS_URL, self.code())
71    }
72
73    /// Severity level for logging and monitoring.
74    pub fn severity(&self) -> Severity {
75        match self {
76            Self::Timeout
77            | Self::Offline
78            | Self::RoutingTableEmpty
79            | Self::ChannelClosed
80            | Self::StreamDropped
81            | Self::GossipSubError(_) => Severity::Warning,
82            Self::UnsupportedProtocol | Self::StoreError(_) | Self::Other(_) => Severity::Error,
83        }
84    }
85
86    /// Whether the client should offer a retry action.
87    pub fn is_retryable(&self) -> bool {
88        matches!(
89            self,
90            Self::Timeout | Self::Offline | Self::RoutingTableEmpty | Self::StreamDropped
91        )
92    }
93
94    /// Returns the user-facing message.
95    pub fn user_message(&self) -> String {
96        match self {
97            Self::Timeout => "The network request timed out.".to_string(),
98            Self::Offline => "The node is offline or unreachable.".to_string(),
99            Self::RoutingTableEmpty => "The Kademlia routing table is empty.".to_string(),
100            Self::ChannelClosed => "Internal channel closed.".to_string(),
101            Self::StreamDropped => "Stream dropped by peer.".to_string(),
102            Self::UnsupportedProtocol => "Unsupported protocol.".to_string(),
103            Self::GossipSubError(_) => "A GossipSub operation failed.".to_string(),
104            Self::StoreError(_) => "Kademlia store error.".to_string(),
105            Self::Other(_) => "A network error occurred.".to_string(),
106        }
107    }
108}