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. A remote application's error is carried by
15/// [`Error::Remote`]; it does not by itself end the session.
16#[derive(Clone, Debug, thiserror::Error)]
17pub enum Error {
18    /// The session or server was closed locally, including by dropping its owner.
19    #[error("wire protocol closed")]
20    Closed,
21
22    /// The operation's absolute deadline expired. Remote work may still run.
23    #[error("wire operation timed out")]
24    Timeout,
25
26    /// The underlying transport failed or the peer reset the session.
27    #[error("wire transport failed: {0}")]
28    Transport(#[from] Arc<transport::Error>),
29
30    /// The peer returned an application error for this request.
31    #[error("wire peer failed the request, code {}: {}", .0.code, .0.msg)]
32    Remote(schema::Error),
33
34    /// The response's `Message` variant does not match the type requested by
35    /// `Promise::wait()`. This does not end the session.
36    #[error("wire response type mismatch: expected {expected}, received {received}")]
37    UnexpectedResponse {
38        /// Expected protobuf message type.
39        expected: &'static str,
40        /// Received protobuf message type.
41        received: &'static str,
42    },
43
44    /// The submitted message cannot be sent from this session's side. Reported
45    /// through the request or reply promise; this error does not end the session.
46    #[error("wire message cannot be sent in this direction: {0}")]
47    WrongDirection(&'static str),
48
49    /// The encoded message exceeds the transport's sending limit.
50    #[error("wire message too large: {0} bytes")]
51    TooLarge(usize),
52
53    /// The peer sent an invalid envelope or payload. The session closes when this
54    /// is detected. Nested payloads are checked only when `recv()` or `wait()`
55    /// reads them.
56    #[error("wire peer sent a malformed message")]
57    Malformed,
58
59    /// A peer request would exceed the session's request limit. Also returned if
60    /// the limit is lowered below usage. Carries the configured request limit.
61    /// This closes the session.
62    #[error("wire inbound request limit exceeded: {0}")]
63    InboundRequestLimitExceeded(usize),
64
65    /// Buffering an incoming envelope would exceed the session's byte limit.
66    /// Also returned if the limit is lowered below usage. Carries the configured
67    /// byte limit. This closes the session.
68    #[error("wire inbound byte limit exceeded: {0}")]
69    InboundByteLimitExceeded(usize),
70}
71
72impl From<transport::Error> for Error {
73    /// Wraps a transport error in an `Arc` so pending promises can share it.
74    fn from(error: transport::Error) -> Self {
75        Self::Transport(Arc::new(error))
76    }
77}
78
79impl From<schema::Error> for Error {
80    /// Wraps the peer's error code and message in `Error::Remote`.
81    fn from(error: schema::Error) -> Self {
82        Self::Remote(error)
83    }
84}
85
86impl From<Infallible> for Error {
87    /// Allows `Promise::wait()` to return `Message` without extracting a variant.
88    fn from(error: Infallible) -> Self {
89        match error {}
90    }
91}
92
93impl Error {
94    /// Whether a session or server ending with this error did so in an orderly
95    /// way, through a local close, a peer reset or the stream ending.
96    pub(super) fn orderly(&self) -> bool {
97        match self {
98            Self::Closed => true,
99            Self::Transport(error) => matches!(
100                **error,
101                transport::Error::SessionReset | transport::Error::Terminated
102            ),
103            _ => false,
104        }
105    }
106
107    /// The error as a log reason, a transport failure named by the transport's
108    /// own error rather than by the wrapping one.
109    pub(super) fn reason(&self) -> &dyn std::fmt::Display {
110        match self {
111            Self::Transport(error) => error.as_ref(),
112            other => other,
113        }
114    }
115}
116
117impl schema::Error {
118    /// Builds an error with a numeric code and a human-readable message.
119    /// Codes from 0x100 are request-specific. Use [`Self::reserved`] for named
120    /// protocol errors.
121    pub fn new(code: u64, msg: impl Into<String>) -> Self {
122        Self {
123            code,
124            msg: msg.into(),
125        }
126    }
127
128    /// Builds an error from a reserved protocol code and a human-readable message.
129    pub fn reserved(code: schema::ReservedErrors, msg: impl Into<String>) -> Self {
130        Self::new(code as u64, msg)
131    }
132}
133
134/// An application failure a request is answered with. The peer dispatches on
135/// the code and may show the message. Codes below 0x100 are the protocol's
136/// [`schema::ReservedErrors`], an application assigns its own from 0x100 up.
137///
138/// Implementing it converts the error into [`schema::Error`], so a handler can
139/// fail a request with `?` and [`super::Responder::fail`] takes it directly.
140pub trait CodedError: std::error::Error {
141    /// Code identifying the failure to the peer.
142    fn code(&self) -> u64;
143}
144
145impl<E: CodedError> From<E> for schema::Error {
146    /// Converts an application error into its wire form, the code as assigned
147    /// and the message as displayed.
148    fn from(error: E) -> Self {
149        debug_assert!(
150            error.code() >= 0x100,
151            "application error code in the reserved range"
152        );
153        Self::new(error.code(), error.to_string())
154    }
155}