Skip to main content

alien_commands/
error.rs

1use alien_error::{AlienError, AlienErrorData};
2use serde::{Deserialize, Serialize};
3
4/// Errors that occur in the commands protocol.
5#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub enum ErrorData {
8    /// Command validation failed or contains invalid data.
9    #[error(
10        code = "INVALID_COMMAND",
11        message = "Invalid command: {message}",
12        retryable = "false",
13        internal = "false",
14        http_status_code = 400
15    )]
16    InvalidCommand {
17        /// Human-readable description of what makes the command invalid
18        message: String,
19    },
20
21    /// Requested command ID was not found.
22    #[error(
23        code = "COMMAND_NOT_FOUND",
24        message = "Command '{command_id}' not found",
25        retryable = "false",
26        internal = "false",
27        http_status_code = 404
28    )]
29    CommandNotFound {
30        /// ID of the command that was not found
31        command_id: String,
32    },
33
34    /// Explicitly requested command target does not exist in the deployment
35    /// (or is not command-capable). Also returned for an empty resource id.
36    ///
37    /// 404 mirrors the crate's other lookup failures (COMMAND_NOT_FOUND,
38    /// LEASE_NOT_FOUND): the named resource does not exist.
39    #[error(
40        code = "COMMAND_TARGET_NOT_FOUND",
41        message = "Command target '{resource_id}' not found in deployment '{deployment_id}'",
42        retryable = "false",
43        internal = "false",
44        http_status_code = 404
45    )]
46    CommandTargetNotFound {
47        /// Resource ID that was requested but not found
48        resource_id: String,
49        /// Deployment the target was looked up in
50        deployment_id: String,
51    },
52
53    /// Single-target shorthand was used but the deployment has multiple
54    /// command-capable targets.
55    ///
56    /// 409 mirrors CONFLICT: the request conflicts with the deployment's
57    /// current state and the client resolves it by naming a target.
58    #[error(
59        code = "COMMAND_TARGET_AMBIGUOUS",
60        message = "Deployment '{deployment_id}' has multiple command-capable targets; specify targetResourceId",
61        retryable = "false",
62        internal = "false",
63        http_status_code = 409
64    )]
65    CommandTargetAmbiguous {
66        /// Deployment with more than one command-capable target
67        deployment_id: String,
68    },
69
70    /// The deployment has no command-capable targets at all.
71    ///
72    /// 422: the request is well-formed but unsatisfiable for this deployment
73    /// (no resource has commands enabled) — unlike 400 (malformed) or 404
74    /// (a specific named thing missing).
75    #[error(
76        code = "NO_COMMAND_TARGETS",
77        message = "Deployment '{deployment_id}' has no command-capable targets",
78        retryable = "false",
79        internal = "false",
80        http_status_code = 422
81    )]
82    NoCommandTargets {
83        /// Deployment without any command-capable targets
84        deployment_id: String,
85    },
86
87    /// A command target resource id contains `:`, which would break the
88    /// pending-index and idempotency-key delimiter grammar.
89    ///
90    /// 400: the request (or a stored target) is malformed. The commands layer
91    /// permits every resource-id character except `:`, which delimits key
92    /// segments.
93    #[error(
94        code = "COMMAND_TARGET_ID_INVALID",
95        message = "Command target id '{resource_id}' is invalid: ids cannot contain ':'",
96        retryable = "false",
97        internal = "false",
98        http_status_code = 400
99    )]
100    CommandTargetIdInvalid {
101        /// The offending resource id
102        resource_id: String,
103    },
104
105    /// Pull command receiver configuration from the environment is missing
106    /// or invalid (e.g. a required `ALIEN_COMMANDS_*` variable is absent).
107    #[error(
108        code = "COMMAND_RECEIVER_CONFIG_INVALID",
109        message = "Command receiver configuration invalid: {message}",
110        retryable = "false",
111        internal = "false",
112        http_status_code = 400
113    )]
114    CommandReceiverConfigInvalid {
115        /// Human-readable description of what is missing or invalid
116        message: String,
117        /// Environment variable that is missing or invalid
118        env_var: String,
119    },
120
121    /// The command receiver bearer token was rejected by the commands API.
122    #[error(
123        code = "COMMAND_RECEIVER_UNAUTHORIZED",
124        message = "Command receiver authorization failed during {operation}",
125        retryable = "false",
126        internal = "false",
127        http_status_code = 401
128    )]
129    CommandReceiverUnauthorized {
130        /// Command API operation that was rejected
131        operation: String,
132        /// Commands API URL that rejected the token
133        url: String,
134    },
135
136    /// The command receiver request was permanently rejected by the commands API.
137    #[error(
138        code = "COMMAND_RECEIVER_REQUEST_REJECTED",
139        message = "Command receiver request was rejected with HTTP {status} during {operation}",
140        retryable = "false",
141        internal = "false",
142        http_status_code = 502
143    )]
144    CommandReceiverRequestRejected {
145        /// Command API operation that was rejected
146        operation: String,
147        /// HTTP status returned by the commands API
148        status: u16,
149        /// Commands API URL that rejected the request
150        url: String,
151    },
152
153    /// Invalid state transition attempted on command.
154    #[error(
155        code = "INVALID_STATE_TRANSITION",
156        message = "Invalid state transition from '{from}' to '{to}'",
157        retryable = "false",
158        internal = "false",
159        http_status_code = 400
160    )]
161    InvalidStateTransition {
162        /// Current state of the command
163        from: String,
164        /// Attempted new state
165        to: String,
166    },
167
168    /// Command has expired and can no longer be processed.
169    #[error(
170        code = "COMMAND_EXPIRED",
171        message = "Command '{command_id}' has expired",
172        retryable = "false",
173        internal = "false",
174        http_status_code = 410
175    )]
176    CommandExpired {
177        /// ID of the expired command
178        command_id: String,
179    },
180
181    /// Storage backend operation failed.
182    #[error(
183        code = "STORAGE_OPERATION_FAILED",
184        message = "Storage operation failed: {message}",
185        retryable = "true",
186        internal = "false"
187    )]
188    StorageOperationFailed {
189        /// Human-readable description of the storage failure
190        message: String,
191        /// Storage operation type (upload, download, etc.)
192        operation: Option<String>,
193        /// Storage path or URL that failed
194        path: Option<String>,
195    },
196
197    /// Key-value store operation failed.
198    #[error(
199        code = "KV_OPERATION_FAILED",
200        message = "KV operation '{operation}' failed on key '{key}': {message}",
201        retryable = "true",
202        internal = "false"
203    )]
204    KvOperationFailed {
205        /// Type of KV operation that failed
206        operation: String,
207        /// Key that was being operated on
208        key: String,
209        /// Human-readable description of the failure
210        message: String,
211    },
212
213    /// Transport dispatch to agent failed.
214    #[error(
215        code = "TRANSPORT_DISPATCH_FAILED",
216        message = "Transport dispatch failed: {message}",
217        retryable = "true",
218        internal = "false"
219    )]
220    TransportDispatchFailed {
221        /// Human-readable description of the dispatch failure
222        message: String,
223        /// Transport type that failed
224        transport_type: Option<String>,
225        /// Target endpoint or agent identifier
226        target: Option<String>,
227    },
228
229    /// Transport rejected a command before accepting it for execution.
230    ///
231    /// Unlike an acknowledgement timeout, this is a definite non-delivery:
232    /// the command server may safely record a terminal delivery failure.
233    #[error(
234        code = "TRANSPORT_DISPATCH_REJECTED",
235        message = "Transport rejected dispatch: {message}",
236        retryable = "false",
237        internal = "false"
238    )]
239    TransportDispatchRejected {
240        /// Safe description that contains no endpoint credentials or body.
241        message: String,
242        /// Transport type that rejected the command.
243        transport_type: Option<String>,
244        /// Command identifier (never a credential-bearing URL).
245        target: Option<String>,
246    },
247
248    /// Command envelope validation or parsing failed.
249    #[error(
250        code = "INVALID_ENVELOPE",
251        message = "Invalid command envelope: {message}",
252        retryable = "false",
253        internal = "false",
254        http_status_code = 400
255    )]
256    InvalidEnvelope {
257        /// Human-readable description of the envelope issue
258        message: String,
259        /// Envelope field that caused the validation failure
260        field: Option<String>,
261    },
262
263    /// Agent reported an error during command processing.
264    #[error(
265        code = "AGENT_ERROR",
266        message = "Agent error: {message}",
267        retryable = "true",
268        internal = "false"
269    )]
270    AgentError {
271        /// Human-readable description of the agent error
272        message: String,
273        /// Agent identifier if available
274        deployment_id: Option<String>,
275    },
276
277    /// Serialization or deserialization operation failed.
278    #[error(
279        code = "SERIALIZATION_FAILED",
280        message = "Serialization failed: {message}",
281        retryable = "false",
282        internal = "true"
283    )]
284    SerializationFailed {
285        /// Human-readable description of the serialization failure
286        message: String,
287        /// Data type that failed to serialize/deserialize
288        data_type: Option<String>,
289    },
290
291    /// HTTP operation failed during command processing.
292    #[error(
293        code = "HTTP_OPERATION_FAILED",
294        message = "HTTP operation failed: {message}",
295        retryable = "true",
296        internal = "false"
297    )]
298    HttpOperationFailed {
299        /// Human-readable description of the HTTP failure
300        message: String,
301        /// HTTP method if available
302        method: Option<String>,
303        /// URL that failed if available
304        url: Option<String>,
305    },
306
307    /// Operation is not supported by the current configuration.
308    #[error(
309        code = "OPERATION_NOT_SUPPORTED",
310        message = "Operation not supported: {message}",
311        retryable = "false",
312        internal = "false",
313        http_status_code = 501
314    )]
315    OperationNotSupported {
316        /// Human-readable description of what operation is not supported
317        message: String,
318        /// Operation type that was attempted
319        operation: Option<String>,
320    },
321
322    /// Resource conflict detected (e.g., concurrent modification).
323    #[error(
324        code = "CONFLICT",
325        message = "Conflict: {message}",
326        retryable = "true",
327        internal = "false",
328        http_status_code = 409
329    )]
330    Conflict {
331        /// Human-readable description of the conflict
332        message: String,
333        /// Resource identifier that has the conflict
334        resource_id: Option<String>,
335    },
336
337    /// Requested lease ID was not found.
338    #[error(
339        code = "LEASE_NOT_FOUND",
340        message = "Lease '{lease_id}' not found",
341        retryable = "false",
342        internal = "false",
343        http_status_code = 404
344    )]
345    LeaseNotFound {
346        /// ID of the lease that was not found
347        lease_id: String,
348    },
349
350    /// Generic commands error for uncommon cases.
351    #[error(
352        code = "COMMANDS_ERROR",
353        message = "Commands protocol error: {message}",
354        retryable = "true",
355        internal = "true"
356    )]
357    Other {
358        /// Human-readable description of the error
359        message: String,
360    },
361}
362
363pub type Error = AlienError<ErrorData>;
364pub type Result<T> = alien_error::Result<T, ErrorData>;