rs_teststand_bridge/error.rs
1//! What can go wrong when the engine lives on someone else's thread.
2
3/// A failure from the host, as distinct from a failure from the engine.
4///
5/// The two are worth telling apart. An [`Engine`](Self::Engine) error means the
6/// engine refused the work and the host is still serving; the other variants
7/// mean the host itself is unavailable, and retrying the same request will not
8/// help.
9#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12 /// The engine refused the work. The host is unaffected.
13 #[error(transparent)]
14 Engine(#[from] rs_teststand::Error),
15
16 /// The engine thread could not be started.
17 #[error("the engine thread could not be started: {reason}")]
18 ThreadNotStarted {
19 /// What the operating system reported.
20 reason: String,
21 },
22
23 /// The engine thread has stopped, so no further work can be run.
24 ///
25 /// Terminal for this host: build a new one.
26 #[error("the engine thread has stopped")]
27 HostStopped,
28
29 /// The work was submitted but the thread ended before answering.
30 #[error("the engine thread ended before returning a result")]
31 ResultLost,
32
33 /// A message's object payload could not be turned into data.
34 ///
35 /// Separate from [`Engine`](Self::Engine) because the engine answered
36 /// perfectly well: what failed was rendering the tree it handed over, and
37 /// the rest of the message is still worth delivering.
38 #[error("a message payload could not be serialized: {0}")]
39 Payload(#[from] serde_json::Error),
40
41 /// The socket carrying events failed.
42 ///
43 /// Separate again from [`Engine`](Self::Engine): the engine is fine and the
44 /// run is unaffected, only the listener is unreachable. A host may well
45 /// choose to keep testing and stop reporting.
46 #[error("the event transport failed: {0}")]
47 Transport(#[from] std::io::Error),
48
49 /// A control frame payload exceeded what the protocol allows.
50 ///
51 /// RFC 6455 section 5.5 caps every control frame at 125 bytes. A peer that
52 /// receives a larger one fails the connection, and the send that caused it
53 /// reports success, so this is raised before the frame goes out.
54 #[error("control frame payload of {bytes} bytes exceeds the {limit}-byte limit")]
55 ControlFrameTooLarge {
56 /// What was offered.
57 bytes: usize,
58 /// What the protocol allows.
59 limit: usize,
60 },
61}
62
63#[cfg(test)]
64mod tests {
65 use super::Error;
66
67 #[test]
68 fn an_engine_failure_keeps_its_own_message() {
69 // The host must not bury what the engine said behind wording of its
70 // own; a caller debugging a refused call needs the engine's text.
71 let engine = rs_teststand::Error::UnexpectedType {
72 expected: "a number",
73 actual: "a string",
74 };
75 let expected = engine.to_string();
76 let wrapped = Error::from(engine);
77 assert_eq!(wrapped.to_string(), expected);
78 }
79
80 #[test]
81 fn a_payload_failure_is_not_reported_as_an_engine_failure() {
82 // The distinction matters to a host deciding whether to retry: the
83 // engine is fine, only this message's object could not be rendered.
84 let parsed = serde_json::from_str::<serde_json::Value>("{");
85 assert!(parsed.is_err(), "an unterminated object is not valid JSON");
86 if let Err(broken) = parsed {
87 let wrapped = Error::from(broken);
88 assert!(matches!(wrapped, Error::Payload(_)), "got {wrapped:?}");
89 assert!(wrapped.to_string().starts_with("a message payload"));
90 }
91 }
92
93 #[test]
94 fn a_stopped_host_says_so_without_blaming_the_engine() {
95 assert_eq!(
96 Error::HostStopped.to_string(),
97 "the engine thread has stopped"
98 );
99 }
100}