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