rivetkit-client 2.3.1

Rust client for RivetKit - the Stateful Serverless Framework for building AI agents, realtime apps, and game servers
Documentation
#[allow(dead_code)]
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const USER_AGENT_VALUE: &str = concat!("ActorClient-Rust/", env!("CARGO_PKG_VERSION"));

// Headers
#[allow(dead_code)]
pub const HEADER_ACTOR_QUERY: &str = "x-rivet-query";
pub const HEADER_ENCODING: &str = "x-rivet-encoding";
pub const HEADER_CONN_PARAMS: &str = "x-rivet-conn-params";
#[allow(dead_code)]
pub const HEADER_ACTOR_ID: &str = "x-rivet-actor";
#[allow(dead_code)]
pub const HEADER_CONN_ID: &str = "x-rivet-conn";
#[allow(dead_code)]
pub const HEADER_CONN_TOKEN: &str = "x-rivet-conn-token";

// Gateway headers
pub const HEADER_RIVET_TARGET: &str = "x-rivet-target";
pub const HEADER_RIVET_ACTOR: &str = "x-rivet-actor";
pub const HEADER_RIVET_TOKEN: &str = "x-rivet-token";
pub const HEADER_RIVET_NAMESPACE: &str = "x-rivet-namespace";

// Paths
pub const PATH_CONNECT_WEBSOCKET: &str = "/connect";
pub const PATH_WEBSOCKET_PREFIX: &str = "/websocket/";

pub type RawWebSocket =
	tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;

// WebSocket protocol prefixes
pub const WS_PROTOCOL_STANDARD: &str = "rivet";
pub const WS_PROTOCOL_TARGET: &str = "rivet_target.";
pub const WS_PROTOCOL_ACTOR: &str = "rivet_actor.";
pub const WS_PROTOCOL_ENCODING: &str = "rivet_encoding.";
pub const WS_PROTOCOL_CONN_PARAMS: &str = "rivet_conn_params.";
pub const WS_PROTOCOL_CONN_ID: &str = "rivet_conn.";
pub const WS_PROTOCOL_CONN_TOKEN: &str = "rivet_conn_token.";
pub const WS_PROTOCOL_TOKEN: &str = "rivet_token.";

#[derive(Debug, Clone, Copy)]
pub enum TransportKind {
	WebSocket,
	Sse,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncodingKind {
	Json,
	Cbor,
	Bare,
}

impl EncodingKind {
	pub fn as_str(&self) -> &str {
		match self {
			EncodingKind::Json => "json",
			EncodingKind::Cbor => "cbor",
			EncodingKind::Bare => "bare",
		}
	}
}

impl Default for EncodingKind {
	fn default() -> Self {
		Self::Bare
	}
}

impl ToString for EncodingKind {
	fn to_string(&self) -> String {
		self.as_str().to_string()
	}
}

// Max size of each entry is 128 bytes
pub type ActorKey = Vec<String>;

/// Serialize an actor key for the engine HTTP API in the canonical
/// slash-escaped format shared by the TS SDK (`serializeActorKey` in
/// `rivetkit/src/actor/keys.ts`) and the runner-side parser
/// (`rivetkit-core/src/registry/envoy_callbacks.rs`):
///
/// - empty key array -> the marker `"/"`
/// - empty segment -> the marker backslash-`0`
/// - backslash and `/` inside a segment are escaped with a leading backslash
/// - segments joined with `/`
///
/// Previously this crate sent `serde_json::to_string(key)` (e.g.
/// `["my-key"]`), which the runner-side parser treated as ONE opaque
/// segment, so `ctx.key()` inside the actor returned
/// `[String("[\"my-key\"]")]` instead of `[String("my-key")]`.
pub fn serialize_actor_key(key: &ActorKey) -> String {
	const KEY_SEPARATOR: char = '/';
	const EMPTY_KEY: &str = "/";

	if key.is_empty() {
		return EMPTY_KEY.to_string();
	}

	key.iter()
		.map(|part| {
			if part.is_empty() {
				return "\\0".to_string();
			}
			let mut escaped = String::with_capacity(part.len());
			for ch in part.chars() {
				if ch == '\\' || ch == KEY_SEPARATOR {
					escaped.push('\\');
				}
				escaped.push(ch);
			}
			escaped
		})
		.collect::<Vec<_>>()
		.join(&KEY_SEPARATOR.to_string())
}

#[cfg(test)]
mod actor_key_tests {
	use super::serialize_actor_key;

	#[test]
	fn matches_the_ts_sdk_format() {
		assert_eq!(serialize_actor_key(&vec![]), "/");
		assert_eq!(serialize_actor_key(&vec!["a".into()]), "a");
		assert_eq!(serialize_actor_key(&vec!["a".into(), "b".into()]), "a/b");
		assert_eq!(serialize_actor_key(&vec!["".into()]), "\\0");
		assert_eq!(serialize_actor_key(&vec!["a/b".into()]), "a\\/b");
		assert_eq!(serialize_actor_key(&vec!["a\\b".into()]), "a\\\\b");
	}
}