Skip to main content

rivet_error/
error.rs

1use crate::INTERNAL_ERROR;
2use crate::schema::RivetErrorSchema;
3use serde::{Deserialize, Serialize};
4use std::{fmt, sync::OnceLock};
5
6static EXPOSE_INTERNAL_ERRORS: OnceLock<bool> = OnceLock::new();
7
8/// Whether `RIVET_EXPOSE_ERRORS=1` asks for internal error details in client-facing replies.
9/// Read once per process.
10pub fn expose_internal_errors() -> bool {
11	*EXPOSE_INTERNAL_ERRORS
12		.get_or_init(|| matches!(std::env::var("RIVET_EXPOSE_ERRORS").as_deref(), Ok("1")))
13}
14
15/// Identifies the actor that was handling work when an error was produced.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct ActorSpecifier {
19	pub actor_id: String,
20	pub generation: u64,
21	#[serde(skip_serializing_if = "Option::is_none")]
22	pub key: Option<String>,
23}
24
25impl ActorSpecifier {
26	pub fn new(actor_id: impl Into<String>, generation: u64) -> Self {
27		Self {
28			actor_id: actor_id.into(),
29			generation,
30			key: None,
31		}
32	}
33
34	pub fn with_key(mut self, key: impl Into<String>) -> Self {
35		self.key = Some(key.into());
36		self
37	}
38}
39
40impl fmt::Display for ActorSpecifier {
41	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42		match &self.key {
43			Some(key) => write!(
44				f,
45				"actor {} generation {} key {}",
46				self.actor_id, self.generation, key
47			),
48			None => write!(f, "actor {} generation {}", self.actor_id, self.generation),
49		}
50	}
51}
52
53impl std::error::Error for ActorSpecifier {}
54
55#[derive(Debug, Clone)]
56pub enum RivetErrorKind {
57	Static(&'static RivetErrorSchema),
58	Dynamic {
59		group: String,
60		code: String,
61		default_message: String,
62	},
63}
64
65impl RivetErrorKind {
66	pub fn group(&self) -> &str {
67		match self {
68			Self::Static(schema) => schema.group,
69			Self::Dynamic { group, .. } => group,
70		}
71	}
72
73	pub fn code(&self) -> &str {
74		match self {
75			Self::Static(schema) => schema.code,
76			Self::Dynamic { code, .. } => code,
77		}
78	}
79
80	pub fn default_message(&self) -> &str {
81		match self {
82			Self::Static(schema) => schema.default_message,
83			Self::Dynamic {
84				default_message, ..
85			} => default_message,
86		}
87	}
88
89	pub fn schema(&self) -> Option<&'static RivetErrorSchema> {
90		match self {
91			Self::Static(schema) => Some(schema),
92			Self::Dynamic { .. } => None,
93		}
94	}
95}
96
97#[derive(Debug, Clone)]
98pub struct RivetError {
99	pub kind: RivetErrorKind,
100	pub meta: Option<Box<serde_json::value::RawValue>>,
101	pub message: Option<String>,
102	pub actor: Option<ActorSpecifier>,
103}
104
105impl RivetError {
106	pub fn extract(error: &anyhow::Error) -> Self {
107		// `anyhow::Error::downcast_ref` walks both the chain and any
108		// `.context(...)` wrappers, so this finds an `ActorSpecifier` no matter
109		// where it was attached.
110		let actor = error.downcast_ref::<ActorSpecifier>().cloned();
111		let mut extracted = error
112			.chain()
113			.find_map(|x| x.downcast_ref::<Self>())
114			.cloned()
115			.unwrap_or_else(|| INTERNAL_ERROR.build_internal(error));
116		if extracted.actor.is_none() {
117			extracted.actor = actor;
118		}
119		extracted
120	}
121
122	pub(crate) fn build_internal(error: &anyhow::Error) -> Self {
123		let error_string = format!("{:?}", error);
124		let meta_json = serde_json::json!({
125			"error": error_string
126		});
127		let meta = serde_json::value::to_raw_value(&meta_json).ok();
128
129		Self {
130			kind: RivetErrorKind::Static(&INTERNAL_ERROR),
131			meta,
132			message: expose_internal_errors().then(|| format!("Internal error: {}", error)),
133			actor: None,
134		}
135	}
136
137	pub fn group(&self) -> &str {
138		self.kind.group()
139	}
140
141	pub fn code(&self) -> &str {
142		self.kind.code()
143	}
144
145	pub fn message(&self) -> &str {
146		self.message
147			.as_deref()
148			.unwrap_or_else(|| self.kind.default_message())
149	}
150
151	pub fn metadata(&self) -> Option<serde_json::Value> {
152		self.meta
153			.as_ref()
154			.and_then(|raw| serde_json::from_str(raw.get()).ok())
155	}
156
157	pub fn actor(&self) -> Option<&ActorSpecifier> {
158		self.actor.as_ref()
159	}
160
161	pub fn with_actor(mut self, actor: ActorSpecifier) -> Self {
162		self.actor = Some(actor);
163		self
164	}
165
166	pub fn schema(&self) -> Option<&'static RivetErrorSchema> {
167		self.kind.schema()
168	}
169}
170
171impl fmt::Display for RivetError {
172	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173		write!(f, "{}: {}", self.code(), self.message())
174	}
175}
176
177impl std::error::Error for RivetError {}
178
179impl Serialize for RivetError {
180	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
181	where
182		S: serde::Serializer,
183	{
184		use serde::ser::SerializeStruct;
185
186		let field_count = 3 + usize::from(self.meta.is_some()) + usize::from(self.actor.is_some());
187		let mut state = serializer.serialize_struct("RivetError", field_count)?;
188
189		state.serialize_field("group", self.group())?;
190		state.serialize_field("code", self.code())?;
191		state.serialize_field("message", self.message())?;
192
193		if let Some(meta) = &self.meta {
194			state.serialize_field("meta", meta)?;
195		}
196
197		if let Some(actor) = &self.actor {
198			state.serialize_field("actor", actor)?;
199		}
200
201		state.end()
202	}
203}