Skip to main content

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 `ReadWake` ended a budgeted participant read before a frame arrived;
19    /// the transport remains usable. See `ReadWake` for which reads it reaches.
20    #[error("idle receive interrupted by its owner")]
21    ReadInterrupted,
22
23    /// Establishing, keeping, or recovering a client connection failed.
24    #[error("connection error: {description}")]
25    Connection {
26        /// Human-readable context from the failing connection operation.
27        description: String,
28    },
29
30    /// Encoding, decoding, or interpreting SDK-internal protocol state failed.
31    #[error("protocol error: {description}")]
32    Protocol {
33        /// Human-readable protocol failure context.
34        description: String,
35    },
36
37    /// Serialising an outbound value or deserialising an inbound value failed.
38    #[error("serialization error: {description}")]
39    Serialization {
40        /// Human-readable serialization failure context.
41        description: String,
42    },
43
44    /// A typed message did not satisfy the schema declared for its channel.
45    #[error("type validation failed: {description}")]
46    TypeValidation {
47        /// Human-readable type-validation context.
48        description: String,
49    },
50
51    /// A publish operation encountered application-visible backpressure.
52    #[error("backpressure: {reason}")]
53    Backpressure {
54        /// Human-readable pressure reason.
55        reason: String,
56    },
57
58    /// A publish whose outcome the server could not resolve: it cannot tell
59    /// whether the message was stored.
60    ///
61    /// Decoded from the reply's reason code alone, never from its text. It is
62    /// neither a rejection nor backpressure: the publish may have been stored,
63    /// so the safe response is a retry carrying the SAME idempotency key, which
64    /// answers the original outcome if the row committed.
65    #[error("publish unresolved: {description}")]
66    PublishUnresolved {
67        /// The server's detail for the unresolved publish, verbatim.
68        description: String,
69    },
70
71    /// A conversation operation failed.
72    #[error("conversation {conversation_id}: {description}")]
73    Conversation {
74        /// Application-visible conversation identifier.
75        conversation_id: String,
76        /// Human-readable conversation failure context.
77        description: String,
78    },
79
80    /// Persisted subscription or recovery state could not be read or written.
81    #[error("store error: {description}")]
82    Store {
83        /// Human-readable store failure context.
84        description: String,
85    },
86
87    /// A remote operation ran against a configuration whose transport was never
88    /// connected, so nothing it did could have reached any wire.
89    ///
90    /// `RemoteConfig::new` installs the unconnected in-process transport. Call
91    /// `RemoteConfig::connect_tcp`, `RemoteConfig::connect_tcp_with_auth`,
92    /// `RemoteConfig::connect_websocket`,
93    /// `RemoteConfig::connect_websocket_with_auth`, or (with the `embedded`
94    /// feature) `RemoteConfig::connect_loopback` before publishing,
95    /// subscribing, conversing, or resuming. The SDK refuses here rather than
96    /// encoding a frame it discards and reporting the success that would imply.
97    #[error(
98        "transport not connected: {operation} needs a connected transport; call \
99         RemoteConfig::connect_tcp or RemoteConfig::connect_websocket first"
100    )]
101    NotConnected {
102        /// The SDK operation that refused.
103        operation: String,
104    },
105
106    /// An SDK surface exists but has no machinery behind it, so it refused
107    /// rather than reporting a success it cannot deliver.
108    ///
109    /// This is the SDK being loud about an absent feature. `seam` records where
110    /// the gap is written down (a ledger row, or that nothing records it) and
111    /// `alternative` names the surface that does work today.
112    #[error("{surface} is not wired ({seam}); {alternative}")]
113    Unwired {
114        /// The SDK surface that refused.
115        surface: String,
116        /// Where the gap is recorded, or that nothing records it.
117        seam: String,
118        /// The working surface a caller should reach for instead.
119        alternative: String,
120    },
121}