use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use serde::Serialize;
use serde::de::DeserializeOwned;
use spin::Mutex;
#[cfg(feature = "std")]
use crate::DeliveryAck;
use crate::connection::{
ConnectionEvent, ConnectionLifecycle, ConnectionPool, ConnectionState, ResumeRequest,
SubscriptionId,
};
use crate::embedded::{
EmbeddedChannelHandle, EmbeddedConversationHandle, EmptyLifecycleStream, ReadyResult,
SdkSubscription,
};
use crate::{
ChannelHandle, ConversationHandle, ConversationId, PressureResponse, SchemaValidate, SdkError,
};
pub use super::participant::RemoteParticipantHandle;
use super::config::SdkConfig;
use super::protocol::{
RemoteTransport, WireConversationRequest, WirePublishRequest, WireResumeRequest,
deserialize_payload,
};
use super::{RemoteConfig, ServerAddress};
mod negotiated;
fn unwired_typed_subscribe() -> SdkError {
SdkError::Unwired {
surface: "typed subscribe delivery (ChannelHandle::subscribe on a remote handle)"
.to_string(),
seam: "no ledger row records this gap; W6 is the browser conversation surface, not this"
.to_string(),
alternative: "for real delivery use SubscriptionStream::open plus \
SubscriptionStream::recv_timeout, or \
WebSocketSubscriptionStream::open on the WebSocket transport"
.to_string(),
}
}
#[derive(Debug)]
struct RemoteChannelState {
lifecycle: ConnectionLifecycle,
pool: ConnectionPool,
next_subscription: u64,
}
#[derive(Clone, Debug)]
pub struct RemoteChannelHandle {
server_address: ServerAddress,
channel_name: String,
state: Arc<Mutex<RemoteChannelState>>,
transport: Arc<dyn RemoteTransport>,
}
impl RemoteChannelHandle {
pub fn new(config: &RemoteConfig) -> Result<Self, SdkError> {
Ok(Self {
server_address: config.server_address.clone(),
channel_name: config.channel_name.clone(),
state: Arc::new(Mutex::new(RemoteChannelState {
lifecycle: ConnectionLifecycle::new(),
pool: ConnectionPool::new(config.pool_config)?,
next_subscription: 0,
})),
transport: Arc::clone(&config.transport),
})
}
#[must_use]
pub fn connection_state(&self) -> ConnectionState {
self.state.lock().lifecycle.state().clone()
}
pub fn reconnect_started(&self) -> Result<(), SdkError> {
self.state.lock().lifecycle.reconnect_started()
}
pub fn connected(&self) -> Result<Vec<ResumeRequest>, SdkError> {
let requests = {
let mut state = self.state.lock();
let previous = state.lifecycle.state().clone();
state.lifecycle.connected()?;
let event = ConnectionEvent::new(previous, state.lifecycle.state().clone());
state.pool.resume_requests_for_transition(&event)?
};
for request in &requests {
let wire_request = WireResumeRequest::new(*request);
self.transport.resume(&self.server_address, &wire_request)?;
}
Ok(requests)
}
pub fn track_subscription(&self) -> Result<SubscriptionId, SdkError> {
let mut state = self.state.lock();
let id = SubscriptionId::new(state.next_subscription);
state.next_subscription =
state
.next_subscription
.checked_add(1)
.ok_or_else(|| SdkError::Store {
description: "subscription id overflow".to_string(),
})?;
state.pool.assign_subscription(id)?;
Ok(id)
}
pub fn acknowledge(
&self,
subscription_id: SubscriptionId,
sequence: u64,
) -> Result<(), SdkError> {
let mut state = self.state.lock();
state.pool.acknowledge(subscription_id, sequence)
}
#[must_use]
pub const fn server_address(&self) -> &ServerAddress {
&self.server_address
}
}
impl ChannelHandle for RemoteChannelHandle {
type Subscription<M>
= SdkSubscription<M>
where
M: DeserializeOwned;
type ReplyFuture<'a, Resp>
= ReadyResult<Resp>
where
Self: 'a,
Resp: DeserializeOwned + 'a;
fn publish<M>(&self, message: M) -> Result<PressureResponse, SdkError>
where
M: Serialize + SchemaValidate,
{
let request = WirePublishRequest::new(&self.channel_name, &message)?;
self.transport.publish(&self.server_address, &request)
}
fn subscribe<M>(&self) -> Self::Subscription<M>
where
M: DeserializeOwned,
{
SdkSubscription::error(unwired_typed_subscribe())
}
fn request_reply<Req, Resp>(&self, request: Req) -> ReadyResult<Resp>
where
Req: Serialize + SchemaValidate,
Resp: DeserializeOwned,
{
ReadyResult::new(self.request_reply_blocking(&request))
}
}
impl RemoteChannelHandle {
#[cfg(feature = "std")]
pub fn publish_with_idempotency_key<M>(
&self,
message: &M,
idempotency_key: &str,
) -> Result<DeliveryAck, SdkError>
where
M: Serialize + SchemaValidate,
{
let request =
WirePublishRequest::with_idempotency_key(&self.channel_name, message, idempotency_key)?;
self.transport
.publish_with_delivery(&self.server_address, &request)
}
#[cfg(feature = "std")]
pub fn reply_to<M>(
&self,
request: Option<liminal::protocol::DeliveredId>,
message: &M,
) -> Result<DeliveryAck, SdkError>
where
M: Serialize + SchemaValidate,
{
let reply = WirePublishRequest::reply(&self.channel_name, message, request)?;
self.transport
.publish_with_delivery(&self.server_address, &reply)
}
fn request_reply_blocking<Req, Resp>(&self, request: &Req) -> Result<Resp, SdkError>
where
Req: Serialize + SchemaValidate,
Resp: DeserializeOwned,
{
let conversation_id = ConversationId::new(self.channel_name.clone());
let wire_request = WireConversationRequest::new(&conversation_id, request)?;
let reply = self
.transport
.request_reply_conversation(&self.server_address, &wire_request)?;
deserialize_payload(&reply)
}
}
#[derive(Clone, Debug)]
pub struct RemoteConversationHandle {
server_address: ServerAddress,
conversation_id: ConversationId,
lifecycle: Arc<Mutex<ConnectionLifecycle>>,
transport: Arc<dyn RemoteTransport>,
pending_reply: Arc<Mutex<Option<Vec<u8>>>>,
}
impl RemoteConversationHandle {
#[must_use]
pub fn new(config: &RemoteConfig) -> Self {
Self {
server_address: config.server_address.clone(),
conversation_id: config.conversation_id.clone(),
lifecycle: Arc::new(Mutex::new(ConnectionLifecycle::new())),
transport: Arc::clone(&config.transport),
pending_reply: Arc::new(Mutex::new(None)),
}
}
pub fn request<Req>(&self, request: Req) -> Result<(), SdkError>
where
Req: Serialize,
{
let wire_request = WireConversationRequest::new(&self.conversation_id, &request)?;
let reply = self
.transport
.request_reply_conversation(&self.server_address, &wire_request)?;
*self.pending_reply.lock() = Some(reply);
Ok(())
}
#[must_use]
pub fn connection_state(&self) -> ConnectionState {
self.lifecycle.lock().state().clone()
}
#[must_use]
pub const fn server_address(&self) -> &ServerAddress {
&self.server_address
}
}
impl ConversationHandle for RemoteConversationHandle {
type ReceiveFuture<'a, M>
= ReadyResult<M>
where
Self: 'a,
M: DeserializeOwned + 'a;
type LifecycleStream = EmptyLifecycleStream;
fn send<M>(&self, message: M) -> Result<(), SdkError>
where
M: Serialize,
{
let request = WireConversationRequest::new(&self.conversation_id, &message)?;
self.transport
.send_conversation(&self.server_address, &request)
}
fn receive<M>(&self) -> ReadyResult<M>
where
M: DeserializeOwned,
{
ReadyResult::new(self.receive_blocking())
}
fn lifecycle(&self) -> Self::LifecycleStream {
EmptyLifecycleStream
}
}
impl RemoteConversationHandle {
fn receive_blocking<M>(&self) -> Result<M, SdkError>
where
M: DeserializeOwned,
{
let payload = self
.pending_reply
.lock()
.take()
.ok_or_else(|| SdkError::Conversation {
conversation_id: self.conversation_id.as_str().to_string(),
description: "no correlated reply is pending; call request before receive"
.to_string(),
})?;
deserialize_payload(&payload)
}
}
#[derive(Clone, Debug)]
pub enum SdkChannelHandle {
Embedded(EmbeddedChannelHandle),
Remote(RemoteChannelHandle),
}
impl SdkChannelHandle {
pub fn new(config: &SdkConfig) -> Result<Self, SdkError> {
match config {
SdkConfig::Embedded(config) => Ok(Self::Embedded(EmbeddedChannelHandle::new(config))),
SdkConfig::Remote(config) => Ok(Self::Remote(RemoteChannelHandle::new(config)?)),
}
}
}
impl ChannelHandle for SdkChannelHandle {
type Subscription<M>
= SdkSubscription<M>
where
M: DeserializeOwned;
type ReplyFuture<'a, Resp>
= ReadyResult<Resp>
where
Self: 'a,
Resp: DeserializeOwned + 'a;
fn publish<M>(&self, message: M) -> Result<PressureResponse, SdkError>
where
M: Serialize + SchemaValidate,
{
match self {
Self::Embedded(handle) => handle.publish(message),
Self::Remote(handle) => handle.publish(message),
}
}
fn subscribe<M>(&self) -> Self::Subscription<M>
where
M: DeserializeOwned,
{
match self {
Self::Embedded(handle) => handle.subscribe(),
Self::Remote(handle) => handle.subscribe(),
}
}
fn request_reply<Req, Resp>(&self, request: Req) -> ReadyResult<Resp>
where
Req: Serialize + SchemaValidate,
Resp: DeserializeOwned,
{
match self {
Self::Embedded(handle) => handle.request_reply(request),
Self::Remote(handle) => handle.request_reply(request),
}
}
}
#[derive(Clone, Debug)]
pub enum SdkConversationHandle {
Embedded(EmbeddedConversationHandle),
Remote(RemoteConversationHandle),
}
impl SdkConversationHandle {
pub fn new(config: &SdkConfig) -> Result<Self, SdkError> {
match config {
SdkConfig::Embedded(config) => {
Ok(Self::Embedded(EmbeddedConversationHandle::new(config)))
}
SdkConfig::Remote(config) => Ok(Self::Remote(RemoteConversationHandle::new(config))),
}
}
}
impl ConversationHandle for SdkConversationHandle {
type ReceiveFuture<'a, M>
= ReadyResult<M>
where
Self: 'a,
M: DeserializeOwned + 'a;
type LifecycleStream = EmptyLifecycleStream;
fn send<M>(&self, message: M) -> Result<(), SdkError>
where
M: Serialize,
{
match self {
Self::Embedded(handle) => handle.send(message),
Self::Remote(handle) => handle.send(message),
}
}
fn receive<M>(&self) -> ReadyResult<M>
where
M: DeserializeOwned,
{
match self {
Self::Embedded(handle) => handle.receive(),
Self::Remote(handle) => handle.receive(),
}
}
fn lifecycle(&self) -> Self::LifecycleStream {
EmptyLifecycleStream
}
}