Skip to main content

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}