Skip to main content

rivetkit_client/
handle.rs

1use crate::{
2	common::{EncodingKind, RawWebSocket, TransportKind, HEADER_CONN_PARAMS, HEADER_ENCODING},
3	connection::{start_connection, ActorConnection, ActorConnectionInner},
4	protocol::{codec, query::*},
5	remote_manager::{GatewayTarget, RemoteManager},
6};
7use anyhow::{anyhow, Result};
8use bytes::Bytes;
9use reqwest::{
10	header::{HeaderMap, HeaderValue},
11	Method, Response,
12};
13use serde::Serialize;
14use serde_json::Value as JsonValue;
15use std::{
16	ops::Deref,
17	sync::{Arc, Mutex},
18	time::Duration,
19};
20
21pub use crate::protocol::codec::{QueueSendResult, QueueSendStatus};
22
23#[derive(Debug, Clone, Copy, Default)]
24pub struct SendOpts {}
25
26#[derive(Debug, Clone, Copy, Default)]
27pub struct SendAndWaitOpts {
28	pub timeout: Option<Duration>,
29}
30
31pub type QueueSendOptions = SendAndWaitOpts;
32
33pub struct ActorHandleStateless {
34	remote_manager: RemoteManager,
35	params: Option<JsonValue>,
36	encoding_kind: EncodingKind,
37	// Mutex (not RefCell) so the handle is `Sync` and `&handle` futures
38	// remain `Send` — required to call `.action(...)` from within axum
39	// middleware that needs `Send` futures.
40	query: Mutex<ActorQuery>,
41}
42
43/// An actor handle whose query has already resolved to a concrete actor ID.
44/// Raw requests through this handle never repeat Engine actor lookup.
45#[derive(Clone)]
46pub struct ResolvedActorHandle {
47	remote_manager: RemoteManager,
48	actor_id: String,
49}
50
51impl ResolvedActorHandle {
52	pub fn actor_id(&self) -> &str {
53		&self.actor_id
54	}
55
56	pub async fn fetch(
57		&self,
58		path: &str,
59		method: Method,
60		headers: HeaderMap,
61		body: Option<Bytes>,
62	) -> Result<Response> {
63		let path = normalize_fetch_path(path);
64		let target = GatewayTarget::Direct {
65			actor_id: self.actor_id.clone(),
66		};
67		self.remote_manager
68			.send_request(&target, &path, method, headers, body)
69			.await
70	}
71}
72
73impl ActorHandleStateless {
74	pub fn new(
75		remote_manager: RemoteManager,
76		params: Option<JsonValue>,
77		encoding_kind: EncodingKind,
78		query: ActorQuery,
79	) -> Self {
80		Self {
81			remote_manager,
82			params,
83			encoding_kind,
84			query: Mutex::new(query),
85		}
86	}
87
88	pub async fn action(&self, name: &str, args: Vec<JsonValue>) -> Result<JsonValue> {
89		// Resolve gateway target (query targets are resolved by the gateway).
90		let query = self.query.lock().expect("query lock poisoned").clone();
91		let target = self.remote_manager.gateway_target(&query).await?;
92
93		let body = codec::encode_http_action_request(self.encoding_kind, &args)?;
94
95		let headers = self.protocol_headers()?;
96
97		// Send request via gateway
98		let path = format!("/action/{}", urlencoding::encode(name));
99		let res = self
100			.remote_manager
101			.send_request(
102				&target,
103				&path,
104				Method::POST,
105				headers,
106				Some(Bytes::from(body)),
107			)
108			.await?;
109
110		if !res.status().is_success() {
111			let status = res.status();
112			let body = res.bytes().await?;
113			if let Ok((group, code, message, metadata)) =
114				codec::decode_http_error(self.encoding_kind, &body)
115			{
116				return Err(anyhow!(
117					"action failed ({group}/{code}): {message}, metadata={metadata:?}"
118				));
119			}
120			return Err(anyhow!("action failed: {status}"));
121		}
122
123		// Decode response
124		let output = res.bytes().await?;
125		codec::decode_http_action_response(self.encoding_kind, &output)
126	}
127
128	#[deprecated(
129		note = "queues were deprecated in 3.0 and will be removed in a future version. Use actions and store pending work in SQLite. See https://rivet.dev/actors/docs/deprecated/queues"
130	)]
131	pub async fn send(&self, name: &str, body: impl Serialize, _opts: SendOpts) -> Result<()> {
132		self.send_queue(name, &body, false, None).await.map(|_| ())
133	}
134
135	#[deprecated(
136		note = "queues were deprecated in 3.0 and will be removed in a future version. Use actions and store pending work in SQLite. See https://rivet.dev/actors/docs/deprecated/queues"
137	)]
138	pub async fn send_and_wait(
139		&self,
140		name: &str,
141		body: impl Serialize,
142		opts: SendAndWaitOpts,
143	) -> Result<QueueSendResult> {
144		let result = self.send_queue(name, &body, true, opts.timeout).await?;
145		result.ok_or_else(|| anyhow!("queue wait response missing"))
146	}
147
148	async fn send_queue<T: Serialize>(
149		&self,
150		name: &str,
151		body: &T,
152		wait: bool,
153		timeout: Option<Duration>,
154	) -> Result<Option<QueueSendResult>> {
155		let query = self.query.lock().expect("query lock poisoned").clone();
156		let target = self.remote_manager.gateway_target(&query).await?;
157		let timeout_ms =
158			timeout.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX));
159		let request_body =
160			codec::encode_http_queue_request(self.encoding_kind, name, body, wait, timeout_ms)?;
161
162		let headers = self.protocol_headers()?;
163
164		let path = format!("/queue/{}", urlencoding::encode(name));
165		let res = self
166			.remote_manager
167			.send_request(
168				&target,
169				&path,
170				Method::POST,
171				headers,
172				Some(Bytes::from(request_body)),
173			)
174			.await?;
175
176		if !res.status().is_success() {
177			let status = res.status();
178			let body = res.bytes().await?;
179			if let Ok((group, code, message, metadata)) =
180				codec::decode_http_error(self.encoding_kind, &body)
181			{
182				return Err(anyhow!(
183					"queue send failed ({group}/{code}): {message}, metadata={metadata:?}"
184				));
185			}
186			return Err(anyhow!("queue send failed: {status}"));
187		}
188
189		let body = res.bytes().await?;
190		let result = codec::decode_http_queue_response(self.encoding_kind, &body)?;
191		Ok(wait.then_some(result))
192	}
193
194	pub async fn fetch(
195		&self,
196		path: &str,
197		method: Method,
198		headers: HeaderMap,
199		body: Option<Bytes>,
200	) -> Result<Response> {
201		let query = self.query.lock().expect("query lock poisoned").clone();
202		let target = self.remote_manager.gateway_target(&query).await?;
203		let path = normalize_fetch_path(path);
204		self.remote_manager
205			.send_request(&target, &path, method, headers, body)
206			.await
207	}
208
209	/// Resolves this query to a concrete actor, preserving get-or-create
210	/// behavior for creating handles.
211	pub async fn resolve_handle(&self) -> Result<ResolvedActorHandle> {
212		let query = self.query.lock().expect("query lock poisoned").clone();
213		let actor_id = self.remote_manager.resolve_actor_id(&query).await?;
214		Ok(ResolvedActorHandle {
215			remote_manager: self.remote_manager.clone(),
216			actor_id,
217		})
218	}
219
220	/// Resolves a non-creating actor query without collapsing absence into a
221	/// stringly error. Get-or-create and create queries are rejected because
222	/// their absence has different semantics.
223	pub async fn resolve_optional(&self) -> Result<Option<ResolvedActorHandle>> {
224		let query = self.query.lock().expect("query lock poisoned").clone();
225		let actor_id = match &query {
226			ActorQuery::GetForId { get_for_id } => {
227				self.remote_manager
228					.get_for_id(&get_for_id.name, &get_for_id.actor_id)
229					.await?
230			}
231			ActorQuery::GetForKey { get_for_key } => {
232				self.remote_manager
233					.get_with_key(&get_for_key.name, &get_for_key.key)
234					.await?
235			}
236			ActorQuery::GetOrCreateForKey { .. } | ActorQuery::Create { .. } => {
237				return Err(anyhow!(
238					"resolve_optional is only valid for non-creating actor queries"
239				));
240			}
241		};
242		Ok(actor_id.map(|actor_id| ResolvedActorHandle {
243			remote_manager: self.remote_manager.clone(),
244			actor_id,
245		}))
246	}
247
248	pub async fn web_socket(
249		&self,
250		path: &str,
251		protocols: Option<Vec<String>>,
252	) -> Result<RawWebSocket> {
253		let query = self.query.lock().expect("query lock poisoned").clone();
254		let target = self.remote_manager.gateway_target(&query).await?;
255		self.remote_manager
256			.open_raw_websocket(&target, path, self.params.clone(), protocols)
257			.await
258	}
259
260	pub fn gateway_url(&self) -> Result<String> {
261		let query = self.query.lock().expect("query lock poisoned").clone();
262		self.remote_manager.gateway_url(&query)
263	}
264
265	pub fn get_gateway_url(&self) -> Result<String> {
266		self.gateway_url()
267	}
268
269	pub async fn reload(&self) -> Result<()> {
270		let query = self.query.lock().expect("query lock poisoned").clone();
271		let target = self.remote_manager.gateway_target(&query).await?;
272		let res = self
273			.remote_manager
274			.send_request(
275				&target,
276				"/dynamic/reload",
277				Method::PUT,
278				HeaderMap::new(),
279				None,
280			)
281			.await?;
282		if !res.status().is_success() {
283			let status = res.status();
284			let body = res.text().await.unwrap_or_default();
285			return Err(anyhow!("reload failed with status {status}: {body}"));
286		}
287		Ok(())
288	}
289
290	pub async fn resolve(&self) -> Result<String> {
291		let query = {
292			let Ok(query) = self.query.lock() else {
293				return Err(anyhow!("Failed to lock actor query"));
294			};
295			query.clone()
296		};
297
298		match query {
299			ActorQuery::Create { .. } => Err(anyhow!("actor query cannot be create")),
300			ActorQuery::GetForId { get_for_id } => Ok(get_for_id.actor_id.clone()),
301			_ => {
302				let actor_id = self.remote_manager.resolve_actor_id(&query).await?;
303
304				// Get name from the original query
305				let name = match &query {
306					ActorQuery::GetForKey { get_for_key } => get_for_key.name.clone(),
307					ActorQuery::GetOrCreateForKey {
308						get_or_create_for_key,
309					} => get_or_create_for_key.name.clone(),
310					_ => return Err(anyhow!("unexpected query type")),
311				};
312
313				{
314					let Ok(mut query_mut) = self.query.lock() else {
315						return Err(anyhow!("Failed to lock actor query mutably"));
316					};
317
318					*query_mut = ActorQuery::GetForId {
319						get_for_id: GetForIdRequest {
320							name,
321							actor_id: actor_id.clone(),
322						},
323					};
324				}
325
326				Ok(actor_id)
327			}
328		}
329	}
330
331	fn protocol_headers(&self) -> Result<HeaderMap> {
332		let mut headers = HeaderMap::new();
333		headers.insert(
334			HEADER_ENCODING,
335			HeaderValue::from_str(self.encoding_kind.as_str())?,
336		);
337
338		if let Some(params) = &self.params {
339			headers.insert(
340				HEADER_CONN_PARAMS,
341				HeaderValue::from_str(&serde_json::to_string(params)?)?,
342			);
343		}
344
345		Ok(headers)
346	}
347}
348
349fn normalize_fetch_path(path: &str) -> String {
350	let path = path.trim_start_matches('/');
351	if path.is_empty() {
352		"/request".to_string()
353	} else {
354		format!("/request/{path}")
355	}
356}
357
358pub struct ActorHandle {
359	handle: ActorHandleStateless,
360	remote_manager: RemoteManager,
361	params: Option<JsonValue>,
362	query: ActorQuery,
363	client_shutdown_tx: Arc<tokio::sync::broadcast::Sender<()>>,
364	transport_kind: crate::TransportKind,
365	encoding_kind: EncodingKind,
366}
367
368impl ActorHandle {
369	pub fn new(
370		remote_manager: RemoteManager,
371		params: Option<JsonValue>,
372		query: ActorQuery,
373		client_shutdown_tx: Arc<tokio::sync::broadcast::Sender<()>>,
374		transport_kind: TransportKind,
375		encoding_kind: EncodingKind,
376	) -> Self {
377		let handle = ActorHandleStateless::new(
378			remote_manager.clone(),
379			params.clone(),
380			encoding_kind,
381			query.clone(),
382		);
383
384		Self {
385			handle,
386			remote_manager,
387			params,
388			query,
389			client_shutdown_tx,
390			transport_kind,
391			encoding_kind,
392		}
393	}
394
395	pub fn connect(&self) -> ActorConnection {
396		let conn = ActorConnectionInner::new(
397			self.remote_manager.clone(),
398			self.query.clone(),
399			self.transport_kind,
400			self.encoding_kind,
401			self.params.clone(),
402		);
403
404		let rx = self.client_shutdown_tx.subscribe();
405		start_connection(&conn, rx);
406
407		conn
408	}
409}
410
411impl Deref for ActorHandle {
412	type Target = ActorHandleStateless;
413
414	fn deref(&self) -> &Self::Target {
415		&self.handle
416	}
417}