Skip to main content

termwright_protocol/
error.rs

1//! Error types shared by the protocol modules.
2
3use std::fmt;
4
5/// Untrusted input broke a wire invariant.
6///
7/// `code` mirrors the reference implementation's `ProtocolViolation.code`, so
8/// the cross-language vectors can assert on it.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Violation {
11    /// Stable machine-readable code, e.g. `frame-oversized`.
12    pub code: &'static str,
13    /// Human-readable detail. Never contains the session token.
14    pub detail: String,
15}
16
17impl Violation {
18    pub(crate) fn new(code: &'static str, detail: impl Into<String>) -> Self {
19        Self {
20            code,
21            detail: detail.into(),
22        }
23    }
24}
25
26impl fmt::Display for Violation {
27    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(formatter, "{}: {}", self.code, self.detail)
29    }
30}
31
32impl std::error::Error for Violation {}
33
34/// Why a snapshot was refused.
35///
36/// `code` is the shared taxonomy: `schema`, `unknown-role`, `duplicate-id`,
37/// `missing-parent`, `cycle`, `depth`, `count`, `string-bytes`, `bad-rect`,
38/// `revision`, `bytes`.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct ValidationError {
41    /// Stable machine-readable code.
42    pub code: &'static str,
43    /// Human-readable detail, prefixed with the offending path where known.
44    pub detail: String,
45}
46
47impl ValidationError {
48    pub(crate) fn new(code: &'static str, detail: impl Into<String>) -> Self {
49        Self {
50            code,
51            detail: detail.into(),
52        }
53    }
54}
55
56impl fmt::Display for ValidationError {
57    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(formatter, "{}: {}", self.code, self.detail)
59    }
60}
61
62impl std::error::Error for ValidationError {}
63
64/// Why a wire message was refused: `bad-version`, `malformed` or
65/// `limit-exceeded`.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ParseError {
68    /// One of the three wire error codes.
69    pub code: &'static str,
70    /// Human-readable detail.
71    pub detail: String,
72}
73
74impl ParseError {
75    pub(crate) fn new(code: &'static str, detail: impl Into<String>) -> Self {
76        Self {
77            code,
78            detail: detail.into(),
79        }
80    }
81
82    pub(crate) fn malformed(detail: impl Into<String>) -> Self {
83        Self::new("malformed", detail)
84    }
85}
86
87impl fmt::Display for ParseError {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(formatter, "{}: {}", self.code, self.detail)
90    }
91}
92
93impl std::error::Error for ParseError {}
94
95/// Anything that can go wrong while running a session.
96#[derive(Debug)]
97pub enum Error {
98    /// The wire contract was broken.
99    Protocol(Violation),
100    /// A snapshot failed validation before it was sent.
101    Validation(ValidationError),
102    /// An incoming message could not be parsed.
103    Parse(ParseError),
104    /// The transport failed.
105    Io(std::io::Error),
106    /// The driver did not answer the handshake in time.
107    HandshakeTimeout,
108    /// The driver did not read a frame within the write deadline.
109    ///
110    /// Distinct from [`Error::Io`] so a caller can tell a slow driver — drop
111    /// the frame, keep rendering — from a snapshot refused as invalid, which
112    /// will be refused again for the same tree. Part of a length-prefixed
113    /// frame may already be on the wire, so the session is over, not delayed.
114    WriteTimeout,
115    /// A bounded asynchronous publication queue had no free slot. The
116    /// rejected frame consumed no revision and must receive no marker.
117    PublicationQueueFull,
118    /// The asynchronous publication worker failed or was closed. No later
119    /// revision can be admitted.
120    PublicationWorkerFailed,
121}
122
123impl fmt::Display for Error {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            Error::Protocol(inner) => write!(formatter, "{inner}"),
127            Error::Validation(inner) => write!(formatter, "{inner}"),
128            Error::Parse(inner) => write!(formatter, "{inner}"),
129            Error::Io(inner) => write!(formatter, "io: {inner}"),
130            Error::HandshakeTimeout => write!(formatter, "timed out waiting for hello-ack"),
131            Error::WriteTimeout => {
132                write!(
133                    formatter,
134                    "the driver did not read within the write deadline"
135                )
136            }
137            Error::PublicationQueueFull => write!(formatter, "semantic publication queue full"),
138            Error::PublicationWorkerFailed => {
139                write!(formatter, "semantic publication worker failed")
140            }
141        }
142    }
143}
144
145impl std::error::Error for Error {}
146
147impl From<Violation> for Error {
148    fn from(inner: Violation) -> Self {
149        Error::Protocol(inner)
150    }
151}
152
153impl From<ValidationError> for Error {
154    fn from(inner: ValidationError) -> Self {
155        Error::Validation(inner)
156    }
157}
158
159impl From<ParseError> for Error {
160    fn from(inner: ParseError) -> Self {
161        Error::Parse(inner)
162    }
163}
164
165impl From<std::io::Error> for Error {
166    fn from(inner: std::io::Error) -> Self {
167        Error::Io(inner)
168    }
169}