liminal_sdk/error.rs
1use alloc::string::String;
2
3/// Error taxonomy returned by the Rust SDK API surface.
4///
5/// The SDK keeps application code independent from transport, protocol framing,
6/// and core implementation error types. Concrete embedded and remote adapters
7/// map their internal failures into these variants.
8///
9/// `Clone` is derived so a failure can be both RETAINED and RETURNED at the same
10/// seam. A store failure that bricks a participant handle is exactly that shape:
11/// the error travels to the caller of the failing operation, while a copy stays
12/// behind as the reason every later call reports
13/// `RemoteParticipantError::StateUnavailable`. Without it the cause is
14/// observable exactly once and only by whoever happened to make the failing
15/// call.
16#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
17pub enum SdkError {
18 /// A receiver handle already owns reception, or a direct receive is active.
19 #[error("push reception is already claimed")]
20 ReceiverAlreadyClaimed,
21
22 /// A `ReadWake` ended a budgeted participant read before a frame arrived;
23 /// the transport remains usable. See `ReadWake` for which reads it reaches.
24 #[error("idle receive interrupted by its owner")]
25 ReadInterrupted,
26
27 /// Establishing, keeping, or recovering a client connection failed.
28 #[error("connection error: {description}")]
29 Connection {
30 /// Human-readable context from the failing connection operation.
31 description: String,
32 },
33
34 /// Encoding, decoding, or interpreting SDK-internal protocol state failed.
35 #[error("protocol error: {description}")]
36 Protocol {
37 /// Human-readable protocol failure context.
38 description: String,
39 },
40
41 /// Serialising an outbound value or deserialising an inbound value failed.
42 #[error("serialization error: {description}")]
43 Serialization {
44 /// Human-readable serialization failure context.
45 description: String,
46 },
47
48 /// A typed message did not satisfy the schema declared for its channel.
49 #[error("type validation failed: {description}")]
50 TypeValidation {
51 /// Human-readable type-validation context.
52 description: String,
53 },
54
55 /// A publish operation encountered application-visible backpressure.
56 #[error("backpressure: {reason}")]
57 Backpressure {
58 /// Human-readable pressure reason.
59 reason: String,
60 },
61
62 /// A publish whose outcome the server could not resolve: it cannot tell
63 /// whether the message was stored.
64 ///
65 /// Decoded from the reply's reason code alone, never from its text. It is
66 /// neither a rejection nor backpressure: the publish may have been stored,
67 /// so the safe response is a retry carrying the SAME idempotency key, which
68 /// answers the original outcome if the row committed.
69 #[error("publish unresolved: {description}")]
70 PublishUnresolved {
71 /// The server's detail for the unresolved publish, verbatim.
72 description: String,
73 },
74
75 /// A conversation operation failed.
76 #[error("conversation {conversation_id}: {description}")]
77 Conversation {
78 /// Application-visible conversation identifier.
79 conversation_id: String,
80 /// Human-readable conversation failure context.
81 description: String,
82 },
83
84 /// Persisted subscription or recovery state could not be read or written.
85 #[error("store error: {description}")]
86 Store {
87 /// Human-readable store failure context.
88 description: String,
89 },
90
91 /// A remote operation ran against a configuration whose transport was never
92 /// connected, so nothing it did could have reached any wire.
93 ///
94 /// `RemoteConfig::new` installs the unconnected in-process transport. Call
95 /// `RemoteConfig::connect_tcp`, `RemoteConfig::connect_tcp_with_auth`,
96 /// `RemoteConfig::connect_websocket`,
97 /// `RemoteConfig::connect_websocket_with_auth`, or (with the `embedded`
98 /// feature) `RemoteConfig::connect_loopback` before publishing,
99 /// subscribing, conversing, or resuming. The SDK refuses here rather than
100 /// encoding a frame it discards and reporting the success that would imply.
101 #[error(
102 "transport not connected: {operation} needs a connected transport; call \
103 RemoteConfig::connect_tcp or RemoteConfig::connect_websocket first"
104 )]
105 NotConnected {
106 /// The SDK operation that refused.
107 operation: String,
108 },
109
110 /// An SDK surface exists but has no machinery behind it, so it refused
111 /// rather than reporting a success it cannot deliver.
112 ///
113 /// This is the SDK being loud about an absent feature. `seam` records where
114 /// the gap is written down (a ledger row, or that nothing records it) and
115 /// `alternative` names the surface that does work today.
116 #[error("{surface} is not wired ({seam}); {alternative}")]
117 Unwired {
118 /// The SDK surface that refused.
119 surface: String,
120 /// Where the gap is recorded, or that nothing records it.
121 seam: String,
122 /// The working surface a caller should reach for instead.
123 alternative: String,
124 },
125}