Skip to main content

rivetkit_core/actor/
messages.rs

1use std::collections::HashMap;
2use std::ops::{Deref, DerefMut};
3use std::sync::Arc;
4
5use anyhow::Result;
6use parking_lot::Mutex;
7use rivet_envoy_client::config::{HttpRequestBodyStream, ResponseChunk};
8use serde::{Deserialize, Serialize};
9use tokio::sync::mpsc;
10use tokio_util::sync::CancellationToken;
11
12use crate::actor::connection::ConnHandle;
13use crate::actor::lifecycle_hooks::Reply;
14use crate::actor::schedule::ScheduledFireInfo;
15use crate::actor::task_types::ShutdownKind;
16use crate::error::ProtocolError;
17use crate::types::ConnId;
18use crate::websocket::WebSocket;
19
20#[derive(Clone, Debug)]
21pub struct Request {
22	inner: http::Request<Vec<u8>>,
23	body_stream: Option<Arc<Mutex<Option<HttpRequestBodyStream>>>>,
24	cancel_token: CancellationToken,
25}
26
27impl Request {
28	pub fn new(body: Vec<u8>) -> Self {
29		Self {
30			inner: http::Request::new(body),
31			body_stream: None,
32			cancel_token: CancellationToken::new(),
33		}
34	}
35
36	pub fn from_parts(
37		method: &str,
38		uri: &str,
39		headers: HashMap<String, String>,
40		body: Vec<u8>,
41	) -> Result<Self> {
42		Self::from_parts_with_stream(method, uri, headers, body, None)
43	}
44
45	pub fn from_parts_with_stream(
46		method: &str,
47		uri: &str,
48		headers: HashMap<String, String>,
49		body: Vec<u8>,
50		body_stream: Option<HttpRequestBodyStream>,
51	) -> Result<Self> {
52		let method = method
53			.parse::<http::Method>()
54			.map_err(|error| invalid_http_request("method", format!("{method}: {error}")))?;
55		let uri = uri
56			.parse::<http::Uri>()
57			.map_err(|error| invalid_http_request("uri", format!("{uri}: {error}")))?;
58		let mut request = http::Request::builder()
59			.method(method)
60			.uri(uri)
61			.body(body)?;
62
63		for (name, value) in headers {
64			let header_name: http::header::HeaderName = name
65				.parse()
66				.map_err(|error| invalid_http_request("header name", format!("{name}: {error}")))?;
67			let header_value: http::header::HeaderValue = value.parse().map_err(|error| {
68				invalid_http_request("header value", format!("{name}: {error}"))
69			})?;
70			request.headers_mut().insert(header_name, header_value);
71		}
72
73		Ok(Self {
74			inner: request,
75			body_stream: body_stream.map(|rx| Arc::new(Mutex::new(Some(rx)))),
76			cancel_token: CancellationToken::new(),
77		})
78	}
79
80	pub fn to_parts(&self) -> (String, String, HashMap<String, String>, Vec<u8>) {
81		(
82			self.method().to_string(),
83			self.uri().to_string(),
84			self.headers()
85				.iter()
86				.map(|(name, value)| {
87					(
88						name.to_string(),
89						String::from_utf8_lossy(value.as_bytes()).into_owned(),
90					)
91				})
92				.collect(),
93			self.body().clone(),
94		)
95	}
96
97	pub fn has_body_stream(&self) -> bool {
98		self.body_stream
99			.as_ref()
100			.is_some_and(|body_stream| body_stream.lock().is_some())
101	}
102
103	pub fn take_body_stream(&self) -> Option<HttpRequestBodyStream> {
104		self.body_stream
105			.as_ref()
106			.and_then(|body_stream| body_stream.lock().take())
107	}
108
109	pub fn cancellation_token(&self) -> CancellationToken {
110		self.cancel_token.clone()
111	}
112
113	pub async fn into_buffered(mut self) -> Result<Self> {
114		if let Some(body_stream) = &self.body_stream {
115			let mut body_stream = body_stream.lock().take();
116			if let Some(mut body_stream) = body_stream.take() {
117				while let Some(chunk) = body_stream.recv().await? {
118					self.inner.body_mut().extend_from_slice(&chunk);
119				}
120			}
121		}
122		self.body_stream = None;
123		Ok(self)
124	}
125
126	pub fn into_inner(self) -> http::Request<Vec<u8>> {
127		self.inner
128	}
129
130	pub fn into_body(self) -> Vec<u8> {
131		self.inner.into_body()
132	}
133}
134
135impl Default for Request {
136	fn default() -> Self {
137		Self::new(Vec::new())
138	}
139}
140
141impl Deref for Request {
142	type Target = http::Request<Vec<u8>>;
143
144	fn deref(&self) -> &Self::Target {
145		&self.inner
146	}
147}
148
149impl DerefMut for Request {
150	fn deref_mut(&mut self) -> &mut Self::Target {
151		&mut self.inner
152	}
153}
154
155impl From<http::Request<Vec<u8>>> for Request {
156	fn from(value: http::Request<Vec<u8>>) -> Self {
157		Self {
158			inner: value,
159			body_stream: None,
160			cancel_token: CancellationToken::new(),
161		}
162	}
163}
164
165impl From<Request> for http::Request<Vec<u8>> {
166	fn from(value: Request) -> Self {
167		value.inner
168	}
169}
170
171#[derive(Clone, Debug)]
172pub struct Response(http::Response<Vec<u8>>);
173
174impl Response {
175	pub fn new(body: Vec<u8>) -> Self {
176		Self(http::Response::new(body))
177	}
178
179	pub fn from_parts(
180		status: u16,
181		headers: HashMap<String, String>,
182		body: Vec<u8>,
183	) -> Result<Self> {
184		let mut response = http::Response::new(body);
185		*response.status_mut() = status
186			.try_into()
187			.map_err(|error| invalid_http_response("status", format!("{status}: {error}")))?;
188
189		for (name, value) in headers {
190			let header_name: http::header::HeaderName = name.parse().map_err(|error| {
191				invalid_http_response("header name", format!("{name}: {error}"))
192			})?;
193			let header_value: http::header::HeaderValue = value.parse().map_err(|error| {
194				invalid_http_response("header value", format!("{name}: {error}"))
195			})?;
196			response.headers_mut().insert(header_name, header_value);
197		}
198
199		Ok(Self(response))
200	}
201
202	pub fn to_parts(&self) -> (u16, HashMap<String, String>, Vec<u8>) {
203		(
204			self.status().as_u16(),
205			self.headers()
206				.iter()
207				.map(|(name, value)| {
208					(
209						name.to_string(),
210						String::from_utf8_lossy(value.as_bytes()).into_owned(),
211					)
212				})
213				.collect(),
214			self.body().clone(),
215		)
216	}
217
218	pub fn into_inner(self) -> http::Response<Vec<u8>> {
219		self.0
220	}
221
222	pub fn into_body(self) -> Vec<u8> {
223		self.0.into_body()
224	}
225}
226
227fn invalid_http_request(field: &str, reason: String) -> anyhow::Error {
228	ProtocolError::InvalidHttpRequest {
229		field: field.to_owned(),
230		reason,
231	}
232	.build()
233}
234
235fn invalid_http_response(field: &str, reason: String) -> anyhow::Error {
236	ProtocolError::InvalidHttpResponse {
237		field: field.to_owned(),
238		reason,
239	}
240	.build()
241}
242
243impl Default for Response {
244	fn default() -> Self {
245		Self::new(Vec::new())
246	}
247}
248
249impl Deref for Response {
250	type Target = http::Response<Vec<u8>>;
251
252	fn deref(&self) -> &Self::Target {
253		&self.0
254	}
255}
256
257impl DerefMut for Response {
258	fn deref_mut(&mut self) -> &mut Self::Target {
259		&mut self.0
260	}
261}
262
263impl From<http::Response<Vec<u8>>> for Response {
264	fn from(value: http::Response<Vec<u8>>) -> Self {
265		Self(value)
266	}
267}
268
269impl From<Response> for http::Response<Vec<u8>> {
270	fn from(value: Response) -> Self {
271		value.0
272	}
273}
274
275pub struct StreamingResponse {
276	status: u16,
277	headers: HashMap<String, String>,
278	body_stream: mpsc::Receiver<ResponseChunk>,
279}
280
281impl StreamingResponse {
282	pub fn from_parts(
283		status: u16,
284		headers: HashMap<String, String>,
285		body_stream: mpsc::Receiver<ResponseChunk>,
286	) -> Result<Self> {
287		let status_code: http::StatusCode = status
288			.try_into()
289			.map_err(|error| invalid_http_response("status", format!("{status}: {error}")))?;
290		for (name, value) in &headers {
291			let _: http::header::HeaderName = name.parse().map_err(|error| {
292				invalid_http_response("header name", format!("{name}: {error}"))
293			})?;
294			let _: http::header::HeaderValue = value.parse().map_err(|error| {
295				invalid_http_response("header value", format!("{name}: {error}"))
296			})?;
297		}
298
299		Ok(Self {
300			status: status_code.as_u16(),
301			headers,
302			body_stream,
303		})
304	}
305
306	pub fn into_parts(self) -> (u16, HashMap<String, String>, mpsc::Receiver<ResponseChunk>) {
307		(self.status, self.headers, self.body_stream)
308	}
309}
310
311pub enum ActorHttpResponse {
312	Buffered(Response),
313	Stream(StreamingResponse),
314}
315
316impl From<Response> for ActorHttpResponse {
317	fn from(value: Response) -> Self {
318		Self::Buffered(value)
319	}
320}
321
322impl From<StreamingResponse> for ActorHttpResponse {
323	fn from(value: StreamingResponse) -> Self {
324		Self::Stream(value)
325	}
326}
327
328#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
329pub enum StateDelta {
330	ActorState(Vec<u8>),
331	ConnHibernation { conn: ConnId, bytes: Vec<u8> },
332	ConnHibernationRemoved(ConnId),
333}
334
335/// One workflow-engine KV write that must commit atomically with its actor
336/// state snapshot.
337#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
338pub struct WorkflowKvWrite {
339	pub key: Vec<u8>,
340	pub value: Vec<u8>,
341}
342
343impl StateDelta {
344	pub(crate) fn payload_len(&self) -> usize {
345		match self {
346			Self::ActorState(bytes) | Self::ConnHibernation { bytes, .. } => bytes.len(),
347			Self::ConnHibernationRemoved(_) => 0,
348		}
349	}
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
353pub enum SerializeStateReason {
354	Save,
355	Inspector,
356}
357
358impl SerializeStateReason {
359	pub(crate) fn label(self) -> &'static str {
360		match self {
361			Self::Save => "save",
362			Self::Inspector => "inspector",
363		}
364	}
365}
366
367#[derive(Clone, Debug, PartialEq, Eq)]
368pub enum QueueSendStatus {
369	Completed,
370	TimedOut,
371}
372
373impl QueueSendStatus {
374	pub(crate) fn as_str(&self) -> &'static str {
375		match self {
376			Self::Completed => "completed",
377			Self::TimedOut => "timedOut",
378		}
379	}
380}
381
382#[derive(Clone, Debug, PartialEq, Eq)]
383pub struct QueueSendResult {
384	pub status: QueueSendStatus,
385	pub response: Option<Vec<u8>>,
386}
387
388#[derive(Debug)]
389pub enum ActorEvent {
390	Action {
391		name: String,
392		args: Vec<u8>,
393		conn: Option<ConnHandle>,
394		scheduled_fire: Option<ScheduledFireInfo>,
395		reply: Reply<Vec<u8>>,
396	},
397	HttpRequest {
398		request: Request,
399		reply: Reply<ActorHttpResponse>,
400	},
401	QueueSend {
402		name: String,
403		body: Vec<u8>,
404		conn: ConnHandle,
405		request: Request,
406		wait: bool,
407		timeout_ms: Option<u64>,
408		reply: Reply<QueueSendResult>,
409	},
410	WebSocketOpen {
411		conn: ConnHandle,
412		ws: WebSocket,
413		request: Option<Request>,
414		reply: Reply<()>,
415	},
416	ConnectionPreflight {
417		conn: ConnHandle,
418		params: Vec<u8>,
419		request: Option<Request>,
420		reply: Reply<()>,
421	},
422	ConnectionOpen {
423		conn: ConnHandle,
424		request: Option<Request>,
425		reply: Reply<()>,
426	},
427	ConnectionClosed {
428		conn: ConnHandle,
429	},
430	SubscribeRequest {
431		conn: ConnHandle,
432		event_name: String,
433		reply: Reply<()>,
434	},
435	SerializeState {
436		reason: SerializeStateReason,
437		reply: Reply<Vec<StateDelta>>,
438	},
439	RunGracefulCleanup {
440		reason: ShutdownKind,
441		reply: Reply<()>,
442	},
443	DisconnectConn {
444		conn_id: ConnId,
445		reply: Reply<()>,
446	},
447	#[cfg(test)]
448	BeginSleep,
449	#[cfg(test)]
450	FinalizeSleep {
451		reply: Reply<()>,
452	},
453	#[cfg(test)]
454	Destroy {
455		reply: Reply<()>,
456	},
457	WorkflowHistoryRequested {
458		reply: Reply<Option<Vec<u8>>>,
459	},
460	WorkflowReplayRequested {
461		entry_id: Option<String>,
462		reply: Reply<Option<Vec<u8>>>,
463	},
464}
465
466impl ActorEvent {
467	pub(crate) fn kind(&self) -> &'static str {
468		match self {
469			Self::Action { .. } => "action",
470			Self::HttpRequest { .. } => "http_request",
471			Self::QueueSend { .. } => "queue_send",
472			Self::WebSocketOpen { .. } => "websocket_open",
473			Self::ConnectionPreflight { .. } => "connection_preflight",
474			Self::ConnectionOpen { .. } => "connection_open",
475			Self::ConnectionClosed { .. } => "connection_closed",
476			Self::SubscribeRequest { .. } => "subscribe_request",
477			Self::SerializeState { reason, .. } => match reason {
478				SerializeStateReason::Save => "serialize_state_save",
479				SerializeStateReason::Inspector => "serialize_state_inspector",
480			},
481			Self::RunGracefulCleanup { reason, .. } => match reason {
482				ShutdownKind::Sleep => "run_sleep_cleanup",
483				ShutdownKind::Destroy => "run_destroy_cleanup",
484			},
485			Self::DisconnectConn { .. } => "disconnect_conn",
486			#[cfg(test)]
487			Self::BeginSleep => "begin_sleep",
488			#[cfg(test)]
489			Self::FinalizeSleep { .. } => "finalize_sleep",
490			#[cfg(test)]
491			Self::Destroy { .. } => "destroy",
492			Self::WorkflowHistoryRequested { .. } => "workflow_history_requested",
493			Self::WorkflowReplayRequested { .. } => "workflow_replay_requested",
494		}
495	}
496}
497
498// Test shim keeps moved tests in crate-root tests/ with private-module access.
499#[cfg(test)]
500#[path = "../../tests/messages.rs"]
501mod tests;