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}
116
117impl fmt::Display for Error {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            Error::Protocol(inner) => write!(formatter, "{inner}"),
121            Error::Validation(inner) => write!(formatter, "{inner}"),
122            Error::Parse(inner) => write!(formatter, "{inner}"),
123            Error::Io(inner) => write!(formatter, "io: {inner}"),
124            Error::HandshakeTimeout => write!(formatter, "timed out waiting for hello-ack"),
125            Error::WriteTimeout => {
126                write!(
127                    formatter,
128                    "the driver did not read within the write deadline"
129                )
130            }
131        }
132    }
133}
134
135impl std::error::Error for Error {}
136
137impl From<Violation> for Error {
138    fn from(inner: Violation) -> Self {
139        Error::Protocol(inner)
140    }
141}
142
143impl From<ValidationError> for Error {
144    fn from(inner: ValidationError) -> Self {
145        Error::Validation(inner)
146    }
147}
148
149impl From<ParseError> for Error {
150    fn from(inner: ParseError) -> Self {
151        Error::Parse(inner)
152    }
153}
154
155impl From<std::io::Error> for Error {
156    fn from(inner: std::io::Error) -> Self {
157        Error::Io(inner)
158    }
159}