Skip to main content

ridl_rt/
error.rs

1//! The error strata of ridl §10 that a runtime reports, and the two errors a
2//! generated face returns.
3//!
4//! Stratum 1, the errors an interface declares, is data and travels in a
5//! payload (§10.1). This module holds stratum 2, the contract errors (§10.2),
6//! and stratum 3, the detected infrastructure failures (§10.3). It also holds
7//! [`ClientError`] and [`ProviderError`], which compose those with the port
8//! errors into one error per side of a call (ADR-0021 decision 16).
9
10use crate::payload::Violation;
11use crate::port::{ReadError, SendError, ServeError};
12
13/// A contract error, ridl §10.2. Derived from the contract and never declared.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum Contract {
16    /// `INVALID_VALUE`: a payload breaks its typl constraints.
17    InvalidValue(Violation),
18    /// `PRECONDITION_FAILED`: a `require` clause evaluates to false.
19    PreconditionFailed,
20    /// `CONTRACT_BROKEN`: an `ensure` clause evaluates to false.
21    ContractBroken,
22    /// `UNKNOWN_INTERACTION`: the peers disagree on an interface number or an
23    /// ordinal.
24    UnknownInteraction,
25}
26
27/// An infrastructure failure that the runtime detected, ridl §10.3.
28#[non_exhaustive]
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum Transport {
31    /// The response bound passed without a reply.
32    Timeout,
33    /// A command received no delivery acknowledgment within its bound.
34    Undelivered,
35    /// The connection to the peer is lost.
36    Down,
37    /// A payload is not a well-formed encoding.
38    Corrupt,
39    /// The providing runtime refused the call at admission and the caller may
40    /// retry later. Crosses the frame as a `response` outcome (frame
41    /// specification §9.6).
42    Busy,
43}
44
45/// The outcome of a call that did not succeed.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum CallError {
48    /// A contract error.
49    Contract(Contract),
50    /// An infrastructure failure.
51    Transport(Transport),
52}
53
54/// The error of a generated client call.
55///
56/// One type for every call of every generated client, so that `?` works with
57/// one type and an application over two generated packages handles one
58/// error. A send failure is here and not in [`CallError`], because it is not
59/// a settlement outcome: nothing was sent, and no provider settled anything.
60#[non_exhaustive]
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum ClientError {
63    /// The call was not sent.
64    Send(SendError),
65    /// The call was sent and its outcome is a failure.
66    Call(CallError),
67    /// The port failed while the outcome was read. Only
68    /// [`ReadError::Detached`] is reachable, because the reply buffer is sized
69    /// from the reply's `MAX_SIZE`; it is kept as what it is and not mapped
70    /// onto a [`Transport`] variant, which would give a local failure a
71    /// frame-level meaning.
72    Read(ReadError),
73}
74
75impl From<SendError> for ClientError {
76    fn from(error: SendError) -> Self {
77        ClientError::Send(error)
78    }
79}
80
81impl From<CallError> for ClientError {
82    fn from(error: CallError) -> Self {
83        ClientError::Call(error)
84    }
85}
86
87impl From<ReadError> for ClientError {
88    fn from(error: ReadError) -> Self {
89        ClientError::Read(error)
90    }
91}
92
93/// The error a generated `serve` resolves to.
94#[non_exhaustive]
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum ProviderError {
97    /// `Handler::serve` refused the interface's members.
98    Serve(ServeError),
99    /// The handler port failed while a claim was read; the claims already
100    /// settled stay settled.
101    Claim(ReadError),
102}
103
104impl From<ServeError> for ProviderError {
105    fn from(error: ServeError) -> Self {
106        ProviderError::Serve(error)
107    }
108}
109
110impl From<ReadError> for ProviderError {
111    fn from(error: ReadError) -> Self {
112        ProviderError::Claim(error)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::{CallError, ClientError, Contract, ProviderError, Transport};
119    use crate::port::{ReadError, SendError, ServeError};
120
121    fn owns_nothing<T: Copy + Eq + core::fmt::Debug + 'static>() {}
122
123    #[test]
124    fn every_error_is_copy_and_owns_nothing() {
125        owns_nothing::<Contract>();
126        owns_nothing::<Transport>();
127        owns_nothing::<CallError>();
128        owns_nothing::<ClientError>();
129        owns_nothing::<ProviderError>();
130    }
131
132    /// `?` over a port error or a call outcome in a function returning
133    /// `ClientError` lands in the variant for that side of the call.
134    #[test]
135    fn each_inner_error_converts_into_its_own_client_variant() {
136        fn send() -> Result<(), ClientError> {
137            Err(SendError::Busy)?
138        }
139        fn call() -> Result<(), ClientError> {
140            Err(CallError::Transport(Transport::Timeout))?
141        }
142        fn read() -> Result<(), ClientError> {
143            Err(ReadError::Detached)?
144        }
145        assert_eq!(send(), Err(ClientError::Send(SendError::Busy)));
146        assert_eq!(
147            call(),
148            Err(ClientError::Call(CallError::Transport(Transport::Timeout)))
149        );
150        assert_eq!(read(), Err(ClientError::Read(ReadError::Detached)));
151    }
152
153    #[test]
154    fn each_inner_error_converts_into_its_own_provider_variant() {
155        fn serve() -> Result<(), ProviderError> {
156            Err(ServeError::NotOwner)?
157        }
158        fn claim() -> Result<(), ProviderError> {
159            Err(ReadError::Detached)?
160        }
161        assert_eq!(serve(), Err(ProviderError::Serve(ServeError::NotOwner)));
162        assert_eq!(claim(), Err(ProviderError::Claim(ReadError::Detached)));
163    }
164}