darkbio_wire/protocol/error.rs
1// wire-rs: encrypted protocol between Ark and host
2// Copyright 2026 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! Errors returned by protocol methods and promises.
8
9use super::schema;
10use crate::transport;
11use std::convert::Infallible;
12use std::sync::Arc;
13
14/// Failure of a protocol operation.
15///
16/// A remote application's error is carried by [`Error::Remote`], and it does
17/// not by itself end the session.
18#[derive(Clone, Debug, thiserror::Error)]
19pub enum Error {
20 /// The session or server was closed locally, including by dropping its owner.
21 #[error("wire protocol closed")]
22 Closed,
23
24 /// The operation's absolute deadline expired.
25 ///
26 /// Remote work may still run.
27 #[error("wire operation timed out")]
28 Timeout,
29
30 /// The underlying transport failed or the peer reset the session.
31 #[error("wire transport failed: {0}")]
32 Transport(#[from] Arc<transport::Error>),
33
34 /// The peer returned an application error for this request.
35 #[error("wire peer failed the request, code {}: {}", .0.code, .0.msg)]
36 Remote(schema::Error),
37
38 /// A [`Message`](super::Message) variant differs from the payload type it
39 /// is converted to.
40 ///
41 /// [`Promise::wait`](super::Promise::wait) returns it for a response of the
42 /// wrong type. This does not end the session.
43 #[error("wire response type mismatch: expected {expected}, received {received}")]
44 UnexpectedResponse {
45 /// Type name of the payload the caller expected.
46 expected: &'static str,
47 /// Type name of the payload that arrived.
48 received: &'static str,
49 },
50
51 /// The submitted message cannot be sent from this session's side.
52 ///
53 /// It is reported through the request or reply promise and does not end
54 /// the session. Converting a [`Message`](super::Message) into the content
55 /// oneof of a direction it cannot travel in fails with this error too.
56 #[error("wire message cannot be sent in this direction: {0}")]
57 WrongDirection(&'static str),
58
59 /// The encoded message exceeds the transport's sending limit,
60 /// [`transport::MAX_MESSAGE_SIZE`].
61 ///
62 /// It carries the encoded size in bytes.
63 #[error("wire message too large: {0} bytes")]
64 TooLarge(usize),
65
66 /// The peer sent an invalid envelope or payload.
67 ///
68 /// The session closes when this is detected. Nested payloads are checked
69 /// only when [`Session::recv`](super::Session::recv) or
70 /// [`Promise::wait`](super::Promise::wait) reads them.
71 #[error("wire peer sent a malformed message")]
72 Malformed,
73
74 /// A peer request would exceed the session's request limit.
75 ///
76 /// It also occurs when the limit is lowered below usage. It carries the
77 /// configured request limit and closes the session.
78 #[error("wire inbound request limit exceeded: {0}")]
79 InboundRequestLimitExceeded(usize),
80
81 /// Buffering an incoming envelope would exceed the session's byte limit.
82 ///
83 /// It also occurs when the limit is lowered below usage. It carries the
84 /// configured byte limit and closes the session.
85 #[error("wire inbound byte limit exceeded: {0}")]
86 InboundByteLimitExceeded(usize),
87}
88
89impl From<transport::Error> for Error {
90 /// Wraps a transport error in an `Arc` so pending promises can share it.
91 fn from(error: transport::Error) -> Self {
92 Self::Transport(Arc::new(error))
93 }
94}
95
96impl From<schema::Error> for Error {
97 /// Wraps the peer's error code and message in [`Error::Remote`].
98 fn from(error: schema::Error) -> Self {
99 Self::Remote(error)
100 }
101}
102
103impl From<Infallible> for Error {
104 /// Allows [`Promise::wait`](super::Promise::wait) to return a
105 /// [`Message`](super::Message) without extracting a variant.
106 fn from(error: Infallible) -> Self {
107 match error {}
108 }
109}
110
111impl Error {
112 /// Checks whether a session or server ending with this error did so in an
113 /// orderly way, through a local close, a peer reset or the stream ending.
114 pub(super) fn orderly(&self) -> bool {
115 match self {
116 Self::Closed => true,
117 Self::Transport(error) => matches!(
118 **error,
119 transport::Error::SessionReset | transport::Error::Terminated
120 ),
121 _ => false,
122 }
123 }
124
125 /// Returns the error as a log reason, naming a transport failure by the
126 /// transport's own error rather than by the wrapping one.
127 pub(super) fn reason(&self) -> &dyn std::fmt::Display {
128 match self {
129 Self::Transport(error) => error.as_ref(),
130 other => other,
131 }
132 }
133}
134
135impl schema::Error {
136 /// Builds an error with a numeric code and a human-readable message.
137 ///
138 /// Codes from `0x100` are request-specific. Use [`Self::reserved`] for
139 /// named protocol errors.
140 pub fn new(code: u64, msg: impl Into<String>) -> Self {
141 Self {
142 code,
143 msg: msg.into(),
144 }
145 }
146
147 /// Builds an error from a reserved protocol code and a human-readable message.
148 pub fn reserved(code: schema::ReservedErrors, msg: impl Into<String>) -> Self {
149 Self::new(code as u64, msg)
150 }
151}
152
153/// Application failure that a request is answered with.
154///
155/// The peer can dispatch on the code and show the message. Codes below `0x100`
156/// are the protocol's [`schema::ReservedErrors`], and an application assigns
157/// its own from `0x100` up.
158///
159/// Implementing it converts the error into [`schema::Error`], so a handler can
160/// fail a request with `?` and [`super::Responder::fail`] takes it directly.
161pub trait CodedError: std::error::Error {
162 /// Returns the code identifying the failure to the peer, `0x100` or above.
163 fn code(&self) -> u64;
164}
165
166impl<E: CodedError> From<E> for schema::Error {
167 /// Converts an application error into its wire form, the code as assigned
168 /// and the message as displayed.
169 ///
170 /// # Panics
171 ///
172 /// Panics in debug builds if the code is in the reserved range below `0x100`.
173 fn from(error: E) -> Self {
174 debug_assert!(
175 error.code() >= 0x100,
176 "application error code in the reserved range"
177 );
178 Self::new(error.code(), error.to_string())
179 }
180}