Skip to main content

rivetkit_core/actor/
messages.rs

1use std::collections::HashMap;
2use std::ops::{Deref, DerefMut};
3
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7use crate::actor::connection::ConnHandle;
8use crate::actor::lifecycle_hooks::Reply;
9use crate::actor::task_types::ShutdownKind;
10use crate::error::ProtocolError;
11use crate::types::ConnId;
12use crate::websocket::WebSocket;
13
14#[derive(Clone, Debug)]
15pub struct Request(http::Request<Vec<u8>>);
16
17impl Request {
18	pub fn new(body: Vec<u8>) -> Self {
19		Self(http::Request::new(body))
20	}
21
22	pub fn from_parts(
23		method: &str,
24		uri: &str,
25		headers: HashMap<String, String>,
26		body: Vec<u8>,
27	) -> Result<Self> {
28		let method = method
29			.parse::<http::Method>()
30			.map_err(|error| invalid_http_request("method", format!("{method}: {error}")))?;
31		let uri = uri
32			.parse::<http::Uri>()
33			.map_err(|error| invalid_http_request("uri", format!("{uri}: {error}")))?;
34		let mut request = http::Request::builder()
35			.method(method)
36			.uri(uri)
37			.body(body)?;
38
39		for (name, value) in headers {
40			let header_name: http::header::HeaderName = name
41				.parse()
42				.map_err(|error| invalid_http_request("header name", format!("{name}: {error}")))?;
43			let header_value: http::header::HeaderValue = value.parse().map_err(|error| {
44				invalid_http_request("header value", format!("{name}: {error}"))
45			})?;
46			request.headers_mut().insert(header_name, header_value);
47		}
48
49		Ok(Self(request))
50	}
51
52	pub fn to_parts(&self) -> (String, String, HashMap<String, String>, Vec<u8>) {
53		(
54			self.method().to_string(),
55			self.uri().to_string(),
56			self.headers()
57				.iter()
58				.map(|(name, value)| {
59					(
60						name.to_string(),
61						String::from_utf8_lossy(value.as_bytes()).into_owned(),
62					)
63				})
64				.collect(),
65			self.body().clone(),
66		)
67	}
68
69	pub fn into_inner(self) -> http::Request<Vec<u8>> {
70		self.0
71	}
72
73	pub fn into_body(self) -> Vec<u8> {
74		self.0.into_body()
75	}
76}
77
78impl Default for Request {
79	fn default() -> Self {
80		Self::new(Vec::new())
81	}
82}
83
84impl Deref for Request {
85	type Target = http::Request<Vec<u8>>;
86
87	fn deref(&self) -> &Self::Target {
88		&self.0
89	}
90}
91
92impl DerefMut for Request {
93	fn deref_mut(&mut self) -> &mut Self::Target {
94		&mut self.0
95	}
96}
97
98impl From<http::Request<Vec<u8>>> for Request {
99	fn from(value: http::Request<Vec<u8>>) -> Self {
100		Self(value)
101	}
102}
103
104impl From<Request> for http::Request<Vec<u8>> {
105	fn from(value: Request) -> Self {
106		value.0
107	}
108}
109
110#[derive(Clone, Debug)]
111pub struct Response(http::Response<Vec<u8>>);
112
113impl Response {
114	pub fn new(body: Vec<u8>) -> Self {
115		Self(http::Response::new(body))
116	}
117
118	pub fn from_parts(
119		status: u16,
120		headers: HashMap<String, String>,
121		body: Vec<u8>,
122	) -> Result<Self> {
123		let mut response = http::Response::new(body);
124		*response.status_mut() = status
125			.try_into()
126			.map_err(|error| invalid_http_response("status", format!("{status}: {error}")))?;
127
128		for (name, value) in headers {
129			let header_name: http::header::HeaderName = name.parse().map_err(|error| {
130				invalid_http_response("header name", format!("{name}: {error}"))
131			})?;
132			let header_value: http::header::HeaderValue = value.parse().map_err(|error| {
133				invalid_http_response("header value", format!("{name}: {error}"))
134			})?;
135			response.headers_mut().insert(header_name, header_value);
136		}
137
138		Ok(Self(response))
139	}
140
141	pub fn to_parts(&self) -> (u16, HashMap<String, String>, Vec<u8>) {
142		(
143			self.status().as_u16(),
144			self.headers()
145				.iter()
146				.map(|(name, value)| {
147					(
148						name.to_string(),
149						String::from_utf8_lossy(value.as_bytes()).into_owned(),
150					)
151				})
152				.collect(),
153			self.body().clone(),
154		)
155	}
156
157	pub fn into_inner(self) -> http::Response<Vec<u8>> {
158		self.0
159	}
160
161	pub fn into_body(self) -> Vec<u8> {
162		self.0.into_body()
163	}
164}
165
166fn invalid_http_request(field: &str, reason: String) -> anyhow::Error {
167	ProtocolError::InvalidHttpRequest {
168		field: field.to_owned(),
169		reason,
170	}
171	.build()
172}
173
174fn invalid_http_response(field: &str, reason: String) -> anyhow::Error {
175	ProtocolError::InvalidHttpResponse {
176		field: field.to_owned(),
177		reason,
178	}
179	.build()
180}
181
182impl Default for Response {
183	fn default() -> Self {
184		Self::new(Vec::new())
185	}
186}
187
188impl Deref for Response {
189	type Target = http::Response<Vec<u8>>;
190
191	fn deref(&self) -> &Self::Target {
192		&self.0
193	}
194}
195
196impl DerefMut for Response {
197	fn deref_mut(&mut self) -> &mut Self::Target {
198		&mut self.0
199	}
200}
201
202impl From<http::Response<Vec<u8>>> for Response {
203	fn from(value: http::Response<Vec<u8>>) -> Self {
204		Self(value)
205	}
206}
207
208impl From<Response> for http::Response<Vec<u8>> {
209	fn from(value: Response) -> Self {
210		value.0
211	}
212}
213
214#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
215pub enum StateDelta {
216	ActorState(Vec<u8>),
217	ConnHibernation { conn: ConnId, bytes: Vec<u8> },
218	ConnHibernationRemoved(ConnId),
219}
220
221impl StateDelta {
222	pub(crate) fn payload_len(&self) -> usize {
223		match self {
224			Self::ActorState(bytes) | Self::ConnHibernation { bytes, .. } => bytes.len(),
225			Self::ConnHibernationRemoved(_) => 0,
226		}
227	}
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
231pub enum SerializeStateReason {
232	Save,
233	Inspector,
234}
235
236impl SerializeStateReason {
237	pub(crate) fn label(self) -> &'static str {
238		match self {
239			Self::Save => "save",
240			Self::Inspector => "inspector",
241		}
242	}
243}
244
245#[derive(Clone, Debug, PartialEq, Eq)]
246pub enum QueueSendStatus {
247	Completed,
248	TimedOut,
249}
250
251impl QueueSendStatus {
252	pub(crate) fn as_str(&self) -> &'static str {
253		match self {
254			Self::Completed => "completed",
255			Self::TimedOut => "timedOut",
256		}
257	}
258}
259
260#[derive(Clone, Debug, PartialEq, Eq)]
261pub struct QueueSendResult {
262	pub status: QueueSendStatus,
263	pub response: Option<Vec<u8>>,
264}
265
266#[derive(Debug)]
267pub enum ActorEvent {
268	Action {
269		name: String,
270		args: Vec<u8>,
271		conn: Option<ConnHandle>,
272		reply: Reply<Vec<u8>>,
273	},
274	HttpRequest {
275		request: Request,
276		reply: Reply<Response>,
277	},
278	QueueSend {
279		name: String,
280		body: Vec<u8>,
281		conn: ConnHandle,
282		request: Request,
283		wait: bool,
284		timeout_ms: Option<u64>,
285		reply: Reply<QueueSendResult>,
286	},
287	WebSocketOpen {
288		conn: ConnHandle,
289		ws: WebSocket,
290		request: Option<Request>,
291		reply: Reply<()>,
292	},
293	ConnectionPreflight {
294		conn: ConnHandle,
295		params: Vec<u8>,
296		request: Option<Request>,
297		reply: Reply<()>,
298	},
299	ConnectionOpen {
300		conn: ConnHandle,
301		request: Option<Request>,
302		reply: Reply<()>,
303	},
304	ConnectionClosed {
305		conn: ConnHandle,
306	},
307	SubscribeRequest {
308		conn: ConnHandle,
309		event_name: String,
310		reply: Reply<()>,
311	},
312	SerializeState {
313		reason: SerializeStateReason,
314		reply: Reply<Vec<StateDelta>>,
315	},
316	RunGracefulCleanup {
317		reason: ShutdownKind,
318		reply: Reply<()>,
319	},
320	DisconnectConn {
321		conn_id: ConnId,
322		reply: Reply<()>,
323	},
324	#[cfg(test)]
325	BeginSleep,
326	#[cfg(test)]
327	FinalizeSleep {
328		reply: Reply<()>,
329	},
330	#[cfg(test)]
331	Destroy {
332		reply: Reply<()>,
333	},
334	WorkflowHistoryRequested {
335		reply: Reply<Option<Vec<u8>>>,
336	},
337	WorkflowReplayRequested {
338		entry_id: Option<String>,
339		reply: Reply<Option<Vec<u8>>>,
340	},
341}
342
343impl ActorEvent {
344	pub(crate) fn kind(&self) -> &'static str {
345		match self {
346			Self::Action { .. } => "action",
347			Self::HttpRequest { .. } => "http_request",
348			Self::QueueSend { .. } => "queue_send",
349			Self::WebSocketOpen { .. } => "websocket_open",
350			Self::ConnectionPreflight { .. } => "connection_preflight",
351			Self::ConnectionOpen { .. } => "connection_open",
352			Self::ConnectionClosed { .. } => "connection_closed",
353			Self::SubscribeRequest { .. } => "subscribe_request",
354			Self::SerializeState { reason, .. } => match reason {
355				SerializeStateReason::Save => "serialize_state_save",
356				SerializeStateReason::Inspector => "serialize_state_inspector",
357			},
358			Self::RunGracefulCleanup { reason, .. } => match reason {
359				ShutdownKind::Sleep => "run_sleep_cleanup",
360				ShutdownKind::Destroy => "run_destroy_cleanup",
361			},
362			Self::DisconnectConn { .. } => "disconnect_conn",
363			#[cfg(test)]
364			Self::BeginSleep => "begin_sleep",
365			#[cfg(test)]
366			Self::FinalizeSleep { .. } => "finalize_sleep",
367			#[cfg(test)]
368			Self::Destroy { .. } => "destroy",
369			Self::WorkflowHistoryRequested { .. } => "workflow_history_requested",
370			Self::WorkflowReplayRequested { .. } => "workflow_replay_requested",
371		}
372	}
373}
374
375// Test shim keeps moved tests in crate-root tests/ with private-module access.
376#[cfg(test)]
377#[path = "../../tests/messages.rs"]
378mod tests;