Skip to main content

rivetkit_core/
error.rs

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
140/// Masks private error messages before serializing them to clients because they
141/// may contain private implementation details or user data.
142pub(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
150/// Drops private error metadata before serializing it to clients because it may
151/// contain private implementation details or user data.
152pub(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}