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