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