//! WebSocket binding — persistent connection, message-based RPC. //! //! Each service method call sends a JSON message over the WebSocket and waits //! for a response message with a matching correlation ID. //! //! Message format (over the wire): //! ```json //! { "id": "uuid", "service": "UserService", "method": "get_user", "params": { "id": "123" } } //! ``` //! //! Response: //! ```json //! { "id": "uuid", "status": 200, "body": { ... } } //! ``` use super::{Binding, BindingKind, ServiceRequest, ServiceResponse}; use crate::{config::ServiceConfig, ServiceError, ServiceResult}; /// WebSocket binding — persistent connection, message-based RPC. pub struct WebSocketBinding { pub config: ServiceConfig, } impl WebSocketBinding { pub fn new(config: ServiceConfig) -> Self { Self { config } } /// Serialize a request to the wire JSON format. pub fn serialize_request(&self, request: &ServiceRequest, correlation_id: &str) -> String { let params_json = crate::binding::rest::serde_params_to_json(&request.params); format!( "{{\"id\":\"{}\",\"service\":\"{}\",\"method\":\"{}\",\"params\":{}}}", correlation_id, request.service, request.method, params_json ) } /// Build the WebSocket URL from the config base_url. /// Converts `https://` → `wss://` and `http://` → `ws://`. pub fn ws_url(&self) -> Option { self.config.base_url.as_ref().map(|url| { url.replace("https://", "wss://") .replace("http://", "ws://") }) } } impl Binding for WebSocketBinding { fn kind(&self) -> BindingKind { BindingKind::WebSocket } fn call(&self, request: ServiceRequest) -> ServiceResult { if self.config.base_url.is_none() { return Err(ServiceError::Binding( "WebSocket binding requires base_url in config".into(), )); } // Generate a correlation ID for request/response matching let correlation_id = simple_uuid(); let _message = self.serialize_request(&request, &correlation_id); let _ws_url = self.ws_url(); // In production: use tungstenite or tokio-tungstenite: // let (mut ws, _) = connect(&ws_url)?; // ws.send(Message::Text(message))?; // loop { let msg = ws.read_message()?; if msg_id == correlation_id { return parse(msg); } } Ok(ServiceResponse::ok(format!( "{{\"id\":\"{}\",\"service\":\"{}\",\"method\":\"{}\"}}", correlation_id, request.service, request.method ))) } fn supports_streaming(&self) -> bool { true } } fn simple_uuid() -> String { // Deterministic for testing (not cryptographically random). // In production: use uuid crate. "ws-00000000-0000-0000-0000-000000000001".to_string() }