Skip to main content

vfsi_core/
error.rs

1//! Structured errors for the low-level NFSv4.1 client.
2
3use std::fmt;
4
5/// NFS4 status used for transport-level failures (there is no NFS status).
6pub const STATUS_TRANSPORT: u32 = u32::MAX;
7
8/// Transport provenance, not a guarantee that an operation is safe to retry.
9/// Unknown failures remain `Other`; messages are never parsed to classify them.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum TransportKind {
13    Timeout,
14    Connection,
15    InvalidReply,
16    Authentication,
17    Other,
18}
19
20/// An error from the low-level client: either an NFS4ERR_* status reported by
21/// the server for a compound operation, or a transport/RPC-level failure.
22///
23/// Callers can inspect [`status`](RpcError::status) directly instead of
24/// parsing error strings.
25///
26/// `op_index` is the index of the failing operation in the caller's request
27/// slice: the batched compound helpers (`vread_native`, `vwrite_native`, `getattr_many`,
28/// ...) translate compound positions to caller-relative indices before
29/// returning. For single-operation helpers it is the position within the
30/// compound (typically 2: `SEQUENCE`, `PUTFH`, op). Transport failures always
31/// report `op_index == 0`.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct RpcError {
34    /// Index of the failing operation within the compound.
35    pub op_index: usize,
36    /// The failing op's NFS4ERR_* status, or [`STATUS_TRANSPORT`].
37    pub status: u32,
38    /// Human-readable context.
39    pub message: String,
40    /// Present only for transport/RPC failures.
41    pub transport_kind: Option<TransportKind>,
42}
43
44impl RpcError {
45    /// An NFS status from a specific compound operation.
46    pub fn op(op_index: usize, status: u32) -> RpcError {
47        RpcError {
48            op_index,
49            status,
50            message: String::new(),
51            transport_kind: None,
52        }
53    }
54
55    /// A transport / RPC-level failure (no NFS status available).
56    pub fn transport(message: impl Into<String>) -> RpcError {
57        Self::transport_with_kind(TransportKind::Other, message)
58    }
59
60    pub fn transport_with_kind(kind: TransportKind, message: impl Into<String>) -> RpcError {
61        RpcError {
62            op_index: 0,
63            status: STATUS_TRANSPORT,
64            message: message.into(),
65            transport_kind: Some(kind),
66        }
67    }
68
69    /// Preserve classification at an I/O boundary without inspecting its text.
70    pub fn from_io(error: std::io::Error) -> Self {
71        use std::io::ErrorKind as K;
72        let kind = match error.kind() {
73            K::TimedOut => TransportKind::Timeout,
74            K::ConnectionRefused
75            | K::ConnectionReset
76            | K::ConnectionAborted
77            | K::NotConnected
78            | K::BrokenPipe => TransportKind::Connection,
79            _ => TransportKind::Other,
80        };
81        Self::transport_with_kind(kind, error.to_string())
82    }
83
84    /// Whether this is a transport failure rather than an NFS status.
85    pub fn is_transport(&self) -> bool {
86        self.status == STATUS_TRANSPORT
87    }
88
89    /// Re-attribute this error to a different operation index (used by the
90    /// batched helpers to translate compound positions to caller indices).
91    pub fn with_op_index(self, index: usize) -> RpcError {
92        RpcError {
93            op_index: index,
94            ..self
95        }
96    }
97}
98
99impl fmt::Display for RpcError {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        if self.is_transport() {
102            write!(f, "transport error: {}", self.message)
103        } else {
104            write!(f, "op {} failed: status {}", self.op_index, self.status)
105        }
106    }
107}
108
109impl std::error::Error for RpcError {}
110
111/// Convenience alias used throughout the low-level client.
112pub type RpcResult<T> = Result<T, RpcError>;