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", "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
138/// Masks private error messages before serializing them to clients because they
139/// may contain private implementation details or user data.
140pub(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
148/// Drops private error metadata before serializing it to clients because it may
149/// contain private implementation details or user data.
150pub(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}