1use alloc::string::{String, ToString};
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7use spin::Mutex;
8
9#[cfg(feature = "std")]
10use crate::DeliveryAck;
11use crate::connection::{
12 ConnectionEvent, ConnectionLifecycle, ConnectionPool, ConnectionState, ResumeRequest,
13 SubscriptionId,
14};
15use crate::embedded::{
16 EmbeddedChannelHandle, EmbeddedConversationHandle, EmptyLifecycleStream, ReadyResult,
17 SdkSubscription,
18};
19use crate::{
20 ChannelHandle, ConversationHandle, ConversationId, PressureResponse, SchemaValidate, SdkError,
21};
22
23pub use super::participant::RemoteParticipantHandle;
24
25use super::config::SdkConfig;
26use super::protocol::{
27 RemoteTransport, WireConversationRequest, WirePublishRequest, WireResumeRequest,
28 deserialize_payload,
29};
30use super::{RemoteConfig, ServerAddress};
31mod negotiated;
32
33fn unwired_typed_subscribe() -> SdkError {
40 SdkError::Unwired {
41 surface: "typed subscribe delivery (ChannelHandle::subscribe on a remote handle)"
42 .to_string(),
43 seam: "no ledger row records this gap; W6 is the browser conversation surface, not this"
44 .to_string(),
45 alternative: "for real delivery use SubscriptionStream::open plus \
46 SubscriptionStream::recv_timeout, or \
47 WebSocketSubscriptionStream::open on the WebSocket transport"
48 .to_string(),
49 }
50}
51
52#[derive(Debug)]
53struct RemoteChannelState {
54 lifecycle: ConnectionLifecycle,
55 pool: ConnectionPool,
56 next_subscription: u64,
57}
58
59#[derive(Clone, Debug)]
67pub struct RemoteChannelHandle {
68 server_address: ServerAddress,
69 channel_name: String,
70 state: Arc<Mutex<RemoteChannelState>>,
71 transport: Arc<dyn RemoteTransport>,
72}
73
74impl RemoteChannelHandle {
75 pub fn new(config: &RemoteConfig) -> Result<Self, SdkError> {
81 Ok(Self {
82 server_address: config.server_address.clone(),
83 channel_name: config.channel_name.clone(),
84 state: Arc::new(Mutex::new(RemoteChannelState {
85 lifecycle: ConnectionLifecycle::new(),
86 pool: ConnectionPool::new(config.pool_config)?,
87 next_subscription: 0,
88 })),
89 transport: Arc::clone(&config.transport),
90 })
91 }
92
93 #[must_use]
95 pub fn connection_state(&self) -> ConnectionState {
96 self.state.lock().lifecycle.state().clone()
97 }
98
99 pub fn reconnect_started(&self) -> Result<(), SdkError> {
105 self.state.lock().lifecycle.reconnect_started()
106 }
107
108 pub fn connected(&self) -> Result<Vec<ResumeRequest>, SdkError> {
114 let requests = {
117 let mut state = self.state.lock();
118 let previous = state.lifecycle.state().clone();
119 state.lifecycle.connected()?;
120 let event = ConnectionEvent::new(previous, state.lifecycle.state().clone());
121 state.pool.resume_requests_for_transition(&event)?
122 };
123 for request in &requests {
124 let wire_request = WireResumeRequest::new(*request);
125 self.transport.resume(&self.server_address, &wire_request)?;
126 }
127 Ok(requests)
128 }
129
130 pub fn track_subscription(&self) -> Result<SubscriptionId, SdkError> {
136 let mut state = self.state.lock();
137 let id = SubscriptionId::new(state.next_subscription);
138 state.next_subscription =
139 state
140 .next_subscription
141 .checked_add(1)
142 .ok_or_else(|| SdkError::Store {
143 description: "subscription id overflow".to_string(),
144 })?;
145 state.pool.assign_subscription(id)?;
146 Ok(id)
147 }
148
149 pub fn acknowledge(
155 &self,
156 subscription_id: SubscriptionId,
157 sequence: u64,
158 ) -> Result<(), SdkError> {
159 let mut state = self.state.lock();
160 state.pool.acknowledge(subscription_id, sequence)
161 }
162
163 #[must_use]
165 pub const fn server_address(&self) -> &ServerAddress {
166 &self.server_address
167 }
168}
169
170impl ChannelHandle for RemoteChannelHandle {
171 type Subscription<M>
172 = SdkSubscription<M>
173 where
174 M: DeserializeOwned;
175
176 type ReplyFuture<'a, Resp>
177 = ReadyResult<Resp>
178 where
179 Self: 'a,
180 Resp: DeserializeOwned + 'a;
181
182 fn publish<M>(&self, message: M) -> Result<PressureResponse, SdkError>
183 where
184 M: Serialize + SchemaValidate,
185 {
186 let request = WirePublishRequest::new(&self.channel_name, &message)?;
187 self.transport.publish(&self.server_address, &request)
188 }
189
190 fn subscribe<M>(&self) -> Self::Subscription<M>
207 where
208 M: DeserializeOwned,
209 {
210 SdkSubscription::error(unwired_typed_subscribe())
211 }
212
213 fn request_reply<Req, Resp>(&self, request: Req) -> ReadyResult<Resp>
214 where
215 Req: Serialize + SchemaValidate,
216 Resp: DeserializeOwned,
217 {
218 ReadyResult::new(self.request_reply_blocking(&request))
219 }
220}
221
222impl RemoteChannelHandle {
223 #[cfg(feature = "std")]
241 pub fn publish_with_idempotency_key<M>(
242 &self,
243 message: &M,
244 idempotency_key: &str,
245 ) -> Result<DeliveryAck, SdkError>
246 where
247 M: Serialize + SchemaValidate,
248 {
249 let request =
250 WirePublishRequest::with_idempotency_key(&self.channel_name, message, idempotency_key)?;
251 self.transport
252 .publish_with_delivery(&self.server_address, &request)
253 }
254
255 #[cfg(feature = "std")]
271 pub fn reply_to<M>(
272 &self,
273 request: Option<liminal::protocol::DeliveredId>,
274 message: &M,
275 ) -> Result<DeliveryAck, SdkError>
276 where
277 M: Serialize + SchemaValidate,
278 {
279 let reply = WirePublishRequest::reply(&self.channel_name, message, request)?;
280 self.transport
281 .publish_with_delivery(&self.server_address, &reply)
282 }
283
284 fn request_reply_blocking<Req, Resp>(&self, request: &Req) -> Result<Resp, SdkError>
298 where
299 Req: Serialize + SchemaValidate,
300 Resp: DeserializeOwned,
301 {
302 let conversation_id = ConversationId::new(self.channel_name.clone());
303 let wire_request = WireConversationRequest::new(&conversation_id, request)?;
304 let reply = self
305 .transport
306 .request_reply_conversation(&self.server_address, &wire_request)?;
307 deserialize_payload(&reply)
308 }
309}
310
311#[derive(Clone)]
318pub struct RemoteConversationHandle {
319 server_address: ServerAddress,
320 conversation_id: ConversationId,
321 lifecycle: Arc<Mutex<ConnectionLifecycle>>,
322 transport: Arc<dyn RemoteTransport>,
323 pending_reply: Arc<Mutex<Option<Vec<u8>>>>,
328}
329
330impl core::fmt::Debug for RemoteConversationHandle {
331 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
332 formatter
333 .debug_struct("RemoteConversationHandle")
334 .field("server_address", &self.server_address)
335 .field("conversation_id", &self.conversation_id)
336 .field(
337 "pending_reply_bytes",
338 &self.pending_reply.lock().as_ref().map(Vec::len),
339 )
340 .finish_non_exhaustive()
341 }
342}
343
344impl RemoteConversationHandle {
345 #[must_use]
347 pub fn new(config: &RemoteConfig) -> Self {
348 Self {
349 server_address: config.server_address.clone(),
350 conversation_id: config.conversation_id.clone(),
351 lifecycle: Arc::new(Mutex::new(ConnectionLifecycle::new())),
352 transport: Arc::clone(&config.transport),
353 pending_reply: Arc::new(Mutex::new(None)),
354 }
355 }
356
357 pub fn request<Req>(&self, request: Req) -> Result<(), SdkError>
373 where
374 Req: Serialize,
375 {
376 let wire_request = WireConversationRequest::new(&self.conversation_id, &request)?;
377 let reply = self
378 .transport
379 .request_reply_conversation(&self.server_address, &wire_request)?;
380 *self.pending_reply.lock() = Some(reply);
381 Ok(())
382 }
383
384 #[must_use]
386 pub fn connection_state(&self) -> ConnectionState {
387 self.lifecycle.lock().state().clone()
388 }
389
390 #[must_use]
392 pub const fn server_address(&self) -> &ServerAddress {
393 &self.server_address
394 }
395}
396
397impl ConversationHandle for RemoteConversationHandle {
398 type ReceiveFuture<'a, M>
399 = ReadyResult<M>
400 where
401 Self: 'a,
402 M: DeserializeOwned + 'a;
403
404 type LifecycleStream = EmptyLifecycleStream;
405
406 fn send<M>(&self, message: M) -> Result<(), SdkError>
407 where
408 M: Serialize,
409 {
410 let request = WireConversationRequest::new(&self.conversation_id, &message)?;
411 self.transport
412 .send_conversation(&self.server_address, &request)
413 }
414
415 fn receive<M>(&self) -> ReadyResult<M>
416 where
417 M: DeserializeOwned,
418 {
419 ReadyResult::new(self.receive_blocking())
420 }
421
422 fn lifecycle(&self) -> Self::LifecycleStream {
423 EmptyLifecycleStream
424 }
425}
426
427impl RemoteConversationHandle {
428 fn receive_blocking<M>(&self) -> Result<M, SdkError>
437 where
438 M: DeserializeOwned,
439 {
440 let payload = self
441 .pending_reply
442 .lock()
443 .take()
444 .ok_or_else(|| SdkError::Conversation {
445 conversation_id: self.conversation_id.as_str().to_string(),
446 description: "no correlated reply is pending; call request before receive"
447 .to_string(),
448 })?;
449 deserialize_payload(&payload)
450 }
451}
452
453#[derive(Clone, Debug)]
455pub enum SdkChannelHandle {
456 Embedded(EmbeddedChannelHandle),
458 Remote(RemoteChannelHandle),
460}
461
462impl SdkChannelHandle {
463 pub fn new(config: &SdkConfig) -> Result<Self, SdkError> {
469 match config {
470 SdkConfig::Embedded(config) => Ok(Self::Embedded(EmbeddedChannelHandle::new(config))),
471 SdkConfig::Remote(config) => Ok(Self::Remote(RemoteChannelHandle::new(config)?)),
472 }
473 }
474}
475
476impl ChannelHandle for SdkChannelHandle {
477 type Subscription<M>
478 = SdkSubscription<M>
479 where
480 M: DeserializeOwned;
481
482 type ReplyFuture<'a, Resp>
483 = ReadyResult<Resp>
484 where
485 Self: 'a,
486 Resp: DeserializeOwned + 'a;
487
488 fn publish<M>(&self, message: M) -> Result<PressureResponse, SdkError>
489 where
490 M: Serialize + SchemaValidate,
491 {
492 match self {
493 Self::Embedded(handle) => handle.publish(message),
494 Self::Remote(handle) => handle.publish(message),
495 }
496 }
497
498 fn subscribe<M>(&self) -> Self::Subscription<M>
511 where
512 M: DeserializeOwned,
513 {
514 match self {
515 Self::Embedded(handle) => handle.subscribe(),
516 Self::Remote(handle) => handle.subscribe(),
517 }
518 }
519
520 fn request_reply<Req, Resp>(&self, request: Req) -> ReadyResult<Resp>
521 where
522 Req: Serialize + SchemaValidate,
523 Resp: DeserializeOwned,
524 {
525 match self {
526 Self::Embedded(handle) => handle.request_reply(request),
527 Self::Remote(handle) => handle.request_reply(request),
528 }
529 }
530}
531
532#[derive(Clone, Debug)]
534pub enum SdkConversationHandle {
535 Embedded(EmbeddedConversationHandle),
537 Remote(RemoteConversationHandle),
539}
540
541impl SdkConversationHandle {
542 pub fn new(config: &SdkConfig) -> Result<Self, SdkError> {
548 match config {
549 SdkConfig::Embedded(config) => {
550 Ok(Self::Embedded(EmbeddedConversationHandle::new(config)))
551 }
552 SdkConfig::Remote(config) => Ok(Self::Remote(RemoteConversationHandle::new(config))),
553 }
554 }
555}
556
557impl ConversationHandle for SdkConversationHandle {
558 type ReceiveFuture<'a, M>
559 = ReadyResult<M>
560 where
561 Self: 'a,
562 M: DeserializeOwned + 'a;
563
564 type LifecycleStream = EmptyLifecycleStream;
565
566 fn send<M>(&self, message: M) -> Result<(), SdkError>
567 where
568 M: Serialize,
569 {
570 match self {
571 Self::Embedded(handle) => handle.send(message),
572 Self::Remote(handle) => handle.send(message),
573 }
574 }
575
576 fn receive<M>(&self) -> ReadyResult<M>
577 where
578 M: DeserializeOwned,
579 {
580 match self {
581 Self::Embedded(handle) => handle.receive(),
582 Self::Remote(handle) => handle.receive(),
583 }
584 }
585
586 fn lifecycle(&self) -> Self::LifecycleStream {
587 EmptyLifecycleStream
588 }
589}