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