1use rivet_error::*;
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4
5static ACTION_NOT_FOUND_SCHEMA: RivetErrorSchema = RivetErrorSchema {
6 group: "actor",
7 code: "action_not_found",
8 default_message: "Action not found",
9 meta_type: None,
10 _macro_marker: MacroMarker { _private: () },
11};
12
13pub fn action_not_found(name: impl Into<String>) -> anyhow::Error {
14 let name = name.into();
15 anyhow::Error::new(RivetError {
16 kind: RivetErrorKind::Static(&ACTION_NOT_FOUND_SCHEMA),
17 meta: None,
18 message: Some(format!("Action `{name}` was not found.")),
19 actor: None,
20 })
21}
22
23pub fn public_error_status_code(group: &str, code: &str) -> Option<u16> {
24 match (group, code) {
25 ("auth", "forbidden") => Some(403),
26 ("actor", "action_not_found") => Some(404),
27 ("actor", "action_timed_out") => Some(408),
28 ("actor", "aborted") => Some(400),
29 (
30 "actor_runtime_socket",
31 "unsupported" | "not_enabled" | "closed" | "database_unavailable",
32 ) => Some(400),
33 ("message", "incoming_too_long" | "outgoing_too_long") => Some(400),
34 ("schedule", _) => Some(400),
35 (
36 "queue",
37 "full"
38 | "message_too_large"
39 | "message_invalid"
40 | "invalid_payload"
41 | "invalid_completion_payload"
42 | "already_completed"
43 | "previous_message_not_completed"
44 | "complete_not_configured"
45 | "timed_out",
46 ) => Some(400),
47 ("kv", _) => Some(400),
48 ("user", _) => Some(400),
49 _ => None,
50 }
51}
52
53#[derive(RivetError, Debug, Clone, Deserialize, Serialize)]
54#[error("schedule")]
55pub enum ScheduleRuntimeError {
56 #[error(
57 "invalid_name",
58 "Schedule name is invalid.",
59 "Schedule name is invalid: {reason}"
60 )]
61 InvalidName { reason: String },
62
63 #[error(
64 "invalid_cron_expression",
65 "Cron expression is invalid.",
66 "Cron expression is invalid: {reason}"
67 )]
68 InvalidCronExpression { reason: String },
69
70 #[error(
71 "invalid_timezone",
72 "Schedule timezone is invalid.",
73 "Schedule timezone '{timezone}' is invalid."
74 )]
75 InvalidTimezone { timezone: String },
76
77 #[error(
78 "invalid_interval",
79 "Schedule interval is invalid.",
80 "Schedule interval must be at least {minimum_ms} ms; received {interval_ms} ms."
81 )]
82 InvalidInterval { interval_ms: i64, minimum_ms: i64 },
83
84 #[error(
85 "invalid_max_history",
86 "Schedule history limit is invalid.",
87 "Schedule maxHistory must be between 0 and {maximum}; received {max_history}."
88 )]
89 InvalidMaxHistory { max_history: i64, maximum: i64 },
90
91 #[error(
92 "max_schedules_exceeded",
93 "Actor schedule limit reached.",
94 "Actor has reached its limit of {maximum} pending schedules."
95 )]
96 MaxSchedulesExceeded { maximum: u32 },
97
98 #[error(
99 "invalid_schedule_row",
100 "Stored schedule data is invalid.",
101 "Stored schedule '{schedule_id}' is invalid: {reason}"
102 )]
103 InvalidScheduleRow { schedule_id: String, reason: String },
104
105 #[error(
106 "interrupted",
107 "Scheduled action was interrupted.",
108 "Scheduled action was interrupted before completion."
109 )]
110 Interrupted,
111}
112
113#[derive(RivetError, Debug, Clone, Deserialize, Serialize)]
114#[error("kv")]
115pub(crate) enum KvRuntimeError {
116 #[error(
117 "value_too_large",
118 "KV value is too large.",
119 "KV value too large ({size} bytes). Limit is {limit} bytes."
120 )]
121 ValueTooLarge { size: usize, limit: usize },
122 #[error(
123 "key_too_large",
124 "KV key is too large.",
125 "KV key too large ({size} bytes). Limit is {limit} bytes."
126 )]
127 KeyTooLarge { size: usize, limit: usize },
128}
129
130pub(crate) fn is_internal_error(group: &str, code: &str) -> bool {
131 (group == "core" || group == "rivetkit") && code == "internal_error"
132}
133
134pub(crate) fn is_client_error_public(group: &str, code: &str) -> bool {
135 public_error_status_code(group, code).is_some()
136}
137
138pub(crate) fn client_error_message<'a>(group: &str, code: &str, message: &'a str) -> &'a str {
141 if is_client_error_public(group, code) {
142 message
143 } else {
144 INTERNAL_ERROR.default_message
145 }
146}
147
148pub(crate) fn client_error_metadata<'a>(
151 group: &str,
152 code: &str,
153 metadata: Option<&'a JsonValue>,
154) -> Option<&'a JsonValue> {
155 if is_client_error_public(group, code) {
156 metadata
157 } else {
158 None
159 }
160}
161
162#[derive(RivetError, Debug, Clone, Deserialize, Serialize)]
163#[error("actor")]
164pub enum ActorLifecycle {
165 #[error("starting", "Actor is starting.")]
166 Starting,
167
168 #[error("not_ready", "Actor is not ready.")]
169 NotReady,
170
171 #[error("stopping", "Actor is stopping.")]
172 Stopping,
173
174 #[error("destroying", "Actor is destroying.")]
175 Destroying,
176
177 #[error("shutdown_timeout", "Actor shutdown timed out.")]
178 ShutdownTimeout,
179
180 #[error("dropped_reply", "Actor reply channel was dropped without a response.")]
181 DroppedReply,
182
183 #[error(
184 "overloaded",
185 "Actor is overloaded.",
186 "Actor channel '{channel}' is overloaded while attempting to {operation} (capacity {capacity})."
187 )]
188 Overloaded {
189 channel: String,
190 capacity: usize,
191 operation: String,
192 },
193}
194
195#[derive(RivetError, Debug, Clone, Deserialize, Serialize)]
196#[error("actor")]
197pub enum ActorRuntime {
198 #[error(
199 "not_configured",
200 "Actor capability is not configured.",
201 "Actor capability '{component}' is not configured."
202 )]
203 NotConfigured { component: String },
204
205 #[error(
206 "not_found",
207 "Actor resource was not found.",
208 "Actor {resource} '{id}' was not found."
209 )]
210 NotFound { resource: String, id: String },
211
212 #[error(
213 "not_registered",
214 "Actor factory is not registered.",
215 "Actor factory '{actor_name}' is not registered."
216 )]
217 NotRegistered { actor_name: String },
218
219 #[error("missing_input", "Actor input is missing.")]
220 MissingInput,
221
222 #[error(
223 "invalid_operation",
224 "Actor operation is invalid.",
225 "Actor operation '{operation}' is invalid: {reason}"
226 )]
227 InvalidOperation { operation: String, reason: String },
228
229 #[error(
230 "panicked",
231 "Actor task panicked.",
232 "Actor task panicked while running {operation}."
233 )]
234 Panicked { operation: String },
235}
236
237#[derive(RivetError, Debug, Clone, Deserialize, Serialize)]
238#[error("protocol")]
239pub(crate) enum ProtocolError {
240 #[error(
241 "invalid_http_request",
242 "Invalid HTTP request.",
243 "Invalid HTTP request {field}: {reason}"
244 )]
245 InvalidHttpRequest { field: String, reason: String },
246
247 #[error(
248 "invalid_http_response",
249 "Invalid HTTP response.",
250 "Invalid HTTP response {field}: {reason}"
251 )]
252 InvalidHttpResponse { field: String, reason: String },
253
254 #[error(
255 "invalid_actor_connect_request",
256 "Invalid actor-connect request.",
257 "Invalid actor-connect request {field}: {reason}"
258 )]
259 InvalidActorConnectRequest { field: String, reason: String },
260
261 #[error(
262 "invalid_persisted_data",
263 "Invalid persisted actor data.",
264 "Invalid persisted {label}: {reason}"
265 )]
266 InvalidPersistedData { label: String, reason: String },
267
268 #[error(
269 "unsupported_encoding",
270 "Unsupported protocol encoding.",
271 "Unsupported protocol encoding '{encoding}'."
272 )]
273 UnsupportedEncoding { encoding: String },
274}
275
276#[derive(RivetError, Debug, Clone, Deserialize, Serialize)]
277#[error("sqlite")]
278pub(crate) enum SqliteRuntimeError {
279 #[error(
280 "unavailable",
281 "SQLite is unavailable.",
282 "Actor database is not available because rivetkit-core was built without the sqlite feature."
283 )]
284 Unavailable,
285
286 #[error("closed", "SQLite database is closed.")]
287 Closed,
288
289 #[error(
290 "not_configured",
291 "SQLite is not configured.",
292 "SQLite {component} is not configured."
293 )]
294 NotConfigured { component: String },
295
296 #[error(
297 "invalid_bind_parameter",
298 "Invalid SQLite bind parameter.",
299 "Invalid SQLite bind parameter {name}: {reason}"
300 )]
301 InvalidBindParameter { name: String, reason: String },
302
303 #[error(
304 "writer_busy",
305 "SQLite writer is busy.",
306 "SQLite writer is busy because a transaction is already open."
307 )]
308 WriterBusy,
309
310 #[error(
311 "remote_unavailable",
312 "Remote SQLite is unavailable.",
313 "Remote SQLite is unavailable: {reason}"
314 )]
315 RemoteUnavailable { reason: String },
316
317 #[error(
318 "remote_execution_failed",
319 "Remote SQLite execution failed.",
320 "Remote SQLite execution failed: {message}"
321 )]
322 RemoteExecutionFailed { message: String },
323
324 #[error(
325 "remote_indeterminate_result",
326 "Remote SQLite result is indeterminate.",
327 "Remote SQLite {operation} may have completed, but the envoy disconnected before returning a result."
328 )]
329 RemoteIndeterminateResult { operation: String },
330
331 #[error(
332 "remote_fence_mismatch",
333 "Remote SQLite generation is stale.",
334 "Remote SQLite generation is stale: {reason}"
335 )]
336 RemoteFenceMismatch { reason: String },
337}