1use std::{collections::HashMap, env, sync::Arc};
2
3use anyhow::{Context, Result};
4use rivetkit_shared_types::endpoint::{is_local_engine_endpoint, parse_endpoint_auth};
5use serde_json::Value as JsonValue;
6
7use crate::{
8 common::{ActorKey, EncodingKind, TransportKind},
9 handle::ActorHandle,
10 protocol::query::*,
11 remote_manager::RemoteManager,
12};
13
14#[derive(Default)]
15pub struct GetWithIdOptions {
16 pub params: Option<JsonValue>,
17}
18
19#[derive(Default)]
20pub struct GetOptions {
21 pub params: Option<JsonValue>,
22}
23
24#[derive(Default)]
25pub struct GetOrCreateOptions {
26 pub params: Option<JsonValue>,
27 pub create_in_region: Option<String>,
28 pub create_with_input: Option<JsonValue>,
29 pub pool_name: Option<String>,
31}
32
33#[derive(Default)]
34pub struct CreateOptions {
35 pub params: Option<JsonValue>,
36 pub region: Option<String>,
37 pub input: Option<JsonValue>,
38 pub pool_name: Option<String>,
40}
41
42pub struct ClientConfig {
45 pub endpoint: String,
49 pub token: Option<String>,
53 pub namespace: Option<String>,
55 pub pool_name: Option<String>,
58 pub encoding: EncodingKind,
59 pub transport: TransportKind,
60 pub headers: Option<HashMap<String, String>>,
61 pub max_input_size: Option<usize>,
62 pub disable_metadata_lookup: bool,
63}
64
65impl ClientConfig {
66 pub fn new(endpoint: impl Into<String>) -> Self {
67 Self {
68 endpoint: endpoint.into(),
69 token: None,
70 namespace: None,
71 pool_name: None,
72 encoding: EncodingKind::Bare,
73 transport: TransportKind::WebSocket,
74 headers: None,
75 max_input_size: None,
76 disable_metadata_lookup: false,
77 }
78 }
79
80 pub fn token(mut self, token: impl Into<String>) -> Self {
81 self.token = Some(token.into());
82 self
83 }
84
85 pub fn token_opt(mut self, token: Option<String>) -> Self {
86 self.token = token;
87 self
88 }
89
90 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
91 self.namespace = Some(namespace.into());
92 self
93 }
94
95 pub fn pool_name(mut self, pool_name: impl Into<String>) -> Self {
96 self.pool_name = Some(pool_name.into());
97 self
98 }
99
100 pub fn encoding(mut self, encoding: EncodingKind) -> Self {
101 self.encoding = encoding;
102 self
103 }
104
105 pub fn transport(mut self, transport: TransportKind) -> Self {
106 self.transport = transport;
107 self
108 }
109
110 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
111 self.headers
112 .get_or_insert_with(HashMap::new)
113 .insert(key.into(), value.into());
114 self
115 }
116
117 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
118 self.headers = Some(headers);
119 self
120 }
121
122 pub fn max_input_size(mut self, max_input_size: usize) -> Self {
123 self.max_input_size = Some(max_input_size);
124 self
125 }
126
127 pub fn disable_metadata_lookup(mut self, disable: bool) -> Self {
128 self.disable_metadata_lookup = disable;
129 self
130 }
131}
132
133pub struct Client {
134 remote_manager: RemoteManager,
135 encoding_kind: EncodingKind,
136 transport_kind: TransportKind,
137 shutdown_tx: Arc<tokio::sync::broadcast::Sender<()>>,
138}
139
140impl Clone for Client {
141 fn clone(&self) -> Self {
142 Self {
143 remote_manager: self.remote_manager.clone(),
144 encoding_kind: self.encoding_kind,
145 transport_kind: self.transport_kind,
146 shutdown_tx: self.shutdown_tx.clone(),
147 }
148 }
149}
150
151impl std::fmt::Debug for Client {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("Client")
154 .field("encoding_kind", &self.encoding_kind)
155 .field("transport_kind", &self.transport_kind)
156 .finish_non_exhaustive()
157 }
158}
159
160struct ResolvedConnection {
163 endpoint: String,
164 token: Option<String>,
165 namespace: String,
166 pool_name: String,
167}
168
169fn resolve_connection(config: &ClientConfig) -> Result<ResolvedConnection> {
174 let parsed = parse_endpoint_auth(&config.endpoint).context("invalid endpoint")?;
175 let configured_token = config
176 .token
177 .clone()
178 .or_else(|| env::var("RIVET_TOKEN").ok());
179 let configured_namespace = config
180 .namespace
181 .clone()
182 .or_else(|| env::var("RIVET_NAMESPACE").ok());
183 if parsed.namespace.is_some() && configured_namespace.is_some() {
184 anyhow::bail!(
185 "cannot specify namespace both in the endpoint URL and as a separate config option"
186 );
187 }
188 if parsed.token.is_some() && configured_token.is_some() {
189 anyhow::bail!(
190 "cannot specify token both in the endpoint URL and as a separate config option"
191 );
192 }
193
194 let token = match parsed.token.or(configured_token) {
196 Some(token) => Some(token),
197 None if is_local_engine_endpoint(&parsed.endpoint) => Some("default".to_owned()),
198 None => None,
199 };
200
201 Ok(ResolvedConnection {
202 endpoint: parsed.endpoint,
203 token,
204 namespace: parsed
205 .namespace
206 .or(configured_namespace)
207 .unwrap_or_else(|| "default".to_owned()),
208 pool_name: config
209 .pool_name
210 .clone()
211 .or_else(|| env::var("RIVET_POOL").ok())
212 .unwrap_or_else(|| "default".to_owned()),
213 })
214}
215
216impl Client {
217 pub fn new(config: ClientConfig) -> Self {
222 let (connection, connection_error) = match resolve_connection(&config) {
223 Ok(connection) => (connection, None),
224 Err(error) => {
225 let connection = ResolvedConnection {
226 endpoint: config.endpoint.clone(),
227 token: config.token.clone(),
228 namespace: "default".to_owned(),
229 pool_name: "default".to_owned(),
230 };
231 (connection, Some(format!("{error:#}")))
232 }
233 };
234
235 let mut remote_manager = RemoteManager::from_config(
236 connection.endpoint,
237 connection.token,
238 connection.namespace,
239 connection.pool_name,
240 config.headers,
241 config.max_input_size,
242 config.disable_metadata_lookup,
243 );
244 if let Some(error) = connection_error {
245 remote_manager = remote_manager.with_connection_error(error);
246 }
247
248 Self {
249 remote_manager,
250 encoding_kind: config.encoding,
251 transport_kind: config.transport,
252 shutdown_tx: Arc::new(tokio::sync::broadcast::channel(1).0),
253 }
254 }
255
256 pub fn from_endpoint(endpoint: impl Into<String>) -> Self {
257 Self::new(ClientConfig::new(endpoint))
258 }
259
260 fn create_handle(&self, params: Option<JsonValue>, query: ActorQuery) -> ActorHandle {
261 let handle = ActorHandle::new(
262 self.remote_manager.clone(),
263 params,
264 query,
265 self.shutdown_tx.clone(),
266 self.transport_kind,
267 self.encoding_kind,
268 );
269
270 handle
271 }
272
273 pub fn get(&self, name: &str, key: ActorKey, opts: GetOptions) -> Result<ActorHandle> {
274 let actor_query = ActorQuery::GetForKey {
275 get_for_key: GetForKeyRequest {
276 name: name.to_string(),
277 key,
278 },
279 };
280
281 let handle = self.create_handle(opts.params, actor_query);
282
283 Ok(handle)
284 }
285
286 pub fn get_for_id(&self, name: &str, actor_id: &str, opts: GetOptions) -> Result<ActorHandle> {
287 let actor_query = ActorQuery::GetForId {
288 get_for_id: GetForIdRequest {
289 name: name.to_string(),
290 actor_id: actor_id.to_string(),
291 },
292 };
293
294 let handle = self.create_handle(opts.params, actor_query);
295
296 Ok(handle)
297 }
298
299 pub fn get_or_create(
300 &self,
301 name: &str,
302 key: ActorKey,
303 opts: GetOrCreateOptions,
304 ) -> Result<ActorHandle> {
305 let input = opts.create_with_input;
306 let region = opts.create_in_region;
307
308 let actor_query = ActorQuery::GetOrCreateForKey {
309 get_or_create_for_key: GetOrCreateRequest {
310 name: name.to_string(),
311 key: key,
312 input,
313 region,
314 pool_name: opts.pool_name,
315 },
316 };
317
318 let handle = self.create_handle(opts.params, actor_query);
319
320 Ok(handle)
321 }
322
323 pub async fn create(
324 &self,
325 name: &str,
326 key: ActorKey,
327 opts: CreateOptions,
328 ) -> Result<ActorHandle> {
329 let input = opts.input;
330 let _region = opts.region;
331
332 let actor_id = self
333 .remote_manager
334 .create_actor(name, &key, input, opts.pool_name)
335 .await?;
336
337 let get_query = ActorQuery::GetForId {
338 get_for_id: GetForIdRequest {
339 name: name.to_string(),
340 actor_id,
341 },
342 };
343
344 let handle = self.create_handle(opts.params, get_query);
345
346 Ok(handle)
347 }
348
349 pub fn disconnect(self) {
350 drop(self)
351 }
352
353 pub fn dispose(self) {
354 self.disconnect()
355 }
356}
357
358impl Drop for Client {
359 fn drop(&mut self) {
360 let _ = self.shutdown_tx.send(());
362 }
363}