tsoracle_client/error.rs
1//
2// ░▀█▀░█▀▀░█▀█░█▀▄░█▀█░█▀▀░█░░░█▀▀
3// ░░█░░▀▀█░█░█░█▀▄░█▀█░█░░░█░░░█▀▀
4// ░░▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀
5//
6// tsoracle — Distributed Timestamp Oracle
7// https://www.tsoracle.rs
8//
9// Copyright (c) 2026 Prisma Risk
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15// https://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22//
23
24#[derive(Debug, thiserror::Error)]
25pub enum ClientError {
26 #[error("no reachable endpoints")]
27 NoReachableEndpoints,
28 #[error("transport: {0}")]
29 Transport(#[from] tonic::transport::Error),
30 /// A [`Self::Transport`] failure observed on one endpoint, fanned out to
31 /// the sibling waiters of a coalesced request chunk. `tonic::transport::Error`
32 /// is not `Clone`, so the originating value cannot be duplicated across
33 /// waiters; this variant carries its `Display` text instead. Distinct
34 /// from [`Self::NoReachableEndpoints`]: a single endpoint's transport
35 /// failed, *not* the whole cluster — collapsing the two would mislead
36 /// tracing/alerting on the receiving side.
37 #[error("transport (fanned out to coalesced waiter): {0}")]
38 TransportFanout(String),
39 #[error("rpc: {0}")]
40 Rpc(#[from] tonic::Status),
41 #[error("invalid endpoint: {0}")]
42 InvalidEndpoint(String),
43 #[error("invalid count: {0}")]
44 InvalidCount(u32),
45 #[error("custom channel connector failed: {0}")]
46 Connector(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
47 /// Local driver task has terminated (typically a panic in the user-supplied
48 /// RPC closure) and can no longer serve requests. Distinct from
49 /// [`Self::NoReachableEndpoints`]: the network is fine, the in-process
50 /// driver is dead. Operators should look for a panic in their `tracing`
51 /// logs, not a network issue.
52 #[error("client driver task is gone; subsequent requests cannot be served by this Client")]
53 DriverGone,
54 #[error("dense sequence outcome uncertain: the request may or may not have committed")]
55 SeqUncertain,
56 #[error("invalid sequence key")]
57 InvalidSeqKey,
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn connector_variant_renders_source_in_display() {
66 // Display must embed the boxed source's message so operators reading
67 // logs see the underlying cause, not just the wrapper.
68 let inner: Box<dyn std::error::Error + Send + Sync + 'static> = "boom".into();
69 let err = ClientError::Connector(inner);
70 assert_eq!(err.to_string(), "custom channel connector failed: boom");
71 }
72
73 #[test]
74 fn connector_variant_exposes_source() {
75 use std::error::Error;
76 let inner: Box<dyn std::error::Error + Send + Sync + 'static> =
77 std::io::Error::other("io-err").into();
78 let err = ClientError::Connector(inner);
79 assert!(err.source().is_some(), "Connector must propagate source()");
80 }
81}