A better Rust ATProto crate

thing to allow reconfiguration using the original sub parameters additionally carried through the actual subscription parameters and drove the trait that way allows easier reconnect logic, etc.

Orual 69e210de f8a7a78a

+76
+20
crates/jacquard-common/src/websocket.rs
··· 409 409 pub fn into_inner(self) -> Pin<Box<dyn n0_future::Sink<WsMessage, Error = StreamError>>> { 410 410 self.0 411 411 } 412 + 413 + /// get a mutable reference to the inner boxed sink 414 + #[cfg(not(target_arch = "wasm32"))] 415 + pub fn get_mut( 416 + &mut self, 417 + ) -> &mut Pin<Box<dyn n0_future::Sink<WsMessage, Error = StreamError> + Send>> { 418 + use std::borrow::BorrowMut; 419 + 420 + self.0.borrow_mut() 421 + } 422 + 423 + /// get a mutable reference to the inner boxed sink 424 + #[cfg(target_arch = "wasm32")] 425 + pub fn get_mut( 426 + &mut self, 427 + ) -> &mut Pin<Box<dyn n0_future::Sink<WsMessage, Error = StreamError> + 'static>> { 428 + use std::borrow::BorrowMut; 429 + 430 + self.0.borrow_mut() 431 + } 412 432 } 413 433 414 434 impl fmt::Debug for WsSink {
+56
crates/jacquard-common/src/xrpc/subscription.rs
··· 216 216 } 217 217 } 218 218 219 + /// Websocket subscriber-sent control message 220 + /// 221 + /// Note: this is not meaningful for atproto event stream endpoints as 222 + /// those do not support control after the fact. Jetstream does, however. 223 + /// 224 + /// If you wish to control an ongoing Jetstream connection, wrap the [`WsSink`] 225 + /// returned from one of the `into_*` methods of the [`SubscriptionStream`] 226 + /// in a [`SubscriptionController`] with the corresponding message implementing 227 + /// this trait as a generic parameter. 228 + pub trait SubscriptionControlMessage: Serialize { 229 + /// The subscription this is associated with 230 + type Subscription: XrpcSubscription; 231 + 232 + /// Encode the control message for transmission 233 + /// 234 + /// Defaults to json text (matches Jetstream) 235 + fn encode(&self) -> Result<WsMessage, StreamError> { 236 + Ok(WsMessage::from( 237 + serde_json::to_string(&self).map_err(StreamError::encode)?, 238 + )) 239 + } 240 + 241 + /// Decode the control message 242 + fn decode<'de>(frame: &'de [u8]) -> Result<Self, StreamError> 243 + where 244 + Self: Deserialize<'de>, 245 + { 246 + Ok(serde_json::from_slice(frame).map_err(StreamError::decode)?) 247 + } 248 + } 249 + 250 + /// Control a websocket stream with a given subscription control message 251 + pub struct SubscriptionController<S: SubscriptionControlMessage> { 252 + controller: WsSink, 253 + _marker: PhantomData<fn() -> S>, 254 + } 255 + 256 + impl<S: SubscriptionControlMessage> SubscriptionController<S> { 257 + /// Create a new subscription controller from a WebSocket sink. 258 + pub fn new(controller: WsSink) -> Self { 259 + Self { 260 + controller, 261 + _marker: PhantomData, 262 + } 263 + } 264 + 265 + /// Configure the upstream connection via the websocket 266 + pub async fn configure(&mut self, params: &S) -> Result<(), StreamError> { 267 + let message = params.encode()?; 268 + 269 + n0_future::SinkExt::send(self.controller.get_mut(), message) 270 + .await 271 + .map_err(StreamError::transport) 272 + } 273 + } 274 + 219 275 /// Typed subscription stream wrapping a WebSocket connection. 220 276 /// 221 277 /// Analogous to `Response<R>` for XRPC but for subscription streams.