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}