Skip to main content

monocoque/zmq/
req.rs

1//! REQ socket implementation.
2
3use super::common::channel_to_io_error;
4use bytes::Bytes;
5use monocoque_core::monitor::{SocketEvent, SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::rt::TcpStream;
7use monocoque_zmtp::SocketType;
8use monocoque_zmtp::req::ReqSocket as InternalReq;
9use std::io;
10
11/// A REQ socket for synchronous request-reply patterns.
12///
13/// REQ sockets enforce strict alternation between send and receive:
14/// - Must call `send()` before `recv()`
15/// - Must call `recv()` before next `send()`
16///
17/// They're used for:
18/// - Synchronous RPC clients
19/// - Request-reply protocols
20/// - Client-server communication
21///
22/// ## ZeroMQ Compatibility
23///
24/// Compatible with `zmq::REQ` and `zmq::REP` sockets from libzmq.
25///
26/// ## Example
27///
28/// ```rust,no_run
29/// use monocoque::zmq::ReqSocket;
30/// use bytes::Bytes;
31///
32/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
33/// // Connect to server
34/// let mut socket = ReqSocket::connect("127.0.0.1:5555").await?;
35///
36/// // Send request
37/// socket.send(vec![Bytes::from("REQUEST")]).await?;
38///
39/// // Must receive before next send
40/// if let Ok(Some(reply)) = socket.recv().await {
41///     println!("Got reply: {:?}", reply);
42/// }
43///
44/// // Now can send again
45/// socket.send(vec![Bytes::from("ANOTHER")]).await?;
46/// if let Ok(Some(reply)) = socket.recv().await {
47///     println!("Got reply: {:?}", reply);
48/// }
49/// # Ok(())
50/// # }
51/// ```
52pub struct ReqSocket<S = TcpStream>
53where
54    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
55{
56    inner: InternalReq<S>,
57    monitor: Option<SocketEventSender>,
58}
59
60impl ReqSocket {
61    /// Connect to a ZeroMQ peer and create a REQ socket.
62    ///
63    /// Supports both TCP and IPC endpoints:
64    /// - TCP: `"tcp://127.0.0.1:5555"` or `"127.0.0.1:5555"`
65    /// - IPC: `"ipc:///tmp/socket.sock"` (Unix only)
66    ///
67    /// # Arguments
68    ///
69    /// * `endpoint` - Endpoint to connect to
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if the connection or handshake fails.
74    ///
75    /// # Example
76    ///
77    /// ```rust,no_run
78    /// use monocoque::zmq::ReqSocket;
79    ///
80    /// # async fn example() -> std::io::Result<()> {
81    /// let socket = ReqSocket::connect("tcp://127.0.0.1:5555").await?;
82    /// # Ok(())
83    /// # }
84    /// ```
85    pub async fn connect(endpoint: &str) -> io::Result<Self> {
86        // Try parsing as endpoint, fall back to raw address
87        let addr = if let Ok(monocoque_core::endpoint::Endpoint::Tcp(a)) =
88            monocoque_core::endpoint::Endpoint::parse(endpoint)
89        {
90            a
91        } else {
92            endpoint
93                .parse::<std::net::SocketAddr>()
94                .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
95        };
96
97        let sock = Self {
98            inner: InternalReq::connect(addr).await?,
99            monitor: None,
100        };
101        sock.emit_event(SocketEvent::Connected(
102            monocoque_core::endpoint::Endpoint::Tcp(addr),
103        ));
104        Ok(sock)
105    }
106
107    /// Check if the socket is currently connected.
108    #[inline]
109    pub fn is_connected(&self) -> bool {
110        self.inner.is_connected()
111    }
112
113    /// Try to reconnect to the stored endpoint.
114    pub async fn try_reconnect(&mut self) -> io::Result<()> {
115        self.inner.try_reconnect().await
116    }
117
118    /// Send with automatic reconnection on network error.
119    pub async fn send_with_reconnect(&mut self, msg: Vec<bytes::Bytes>) -> io::Result<()> {
120        self.inner.send_with_reconnect(msg).await
121    }
122
123    /// Receive with automatic reconnection on EOF or network error.
124    pub async fn recv_with_reconnect(&mut self) -> io::Result<Option<Vec<bytes::Bytes>>> {
125        self.inner.recv_with_reconnect().await
126    }
127
128    /// Connect to a ZeroMQ peer with custom socket options.
129    ///
130    /// This allows configuring timeouts and other options before connection.
131    ///
132    /// # Example
133    ///
134    /// ```rust,no_run
135    /// use monocoque::zmq::ReqSocket;
136    /// use monocoque::SocketOptions;
137    /// use std::time::Duration;
138    ///
139    /// # async fn example() -> std::io::Result<()> {
140    /// let options = SocketOptions::default()
141    ///     .with_send_timeout(Duration::from_secs(5))
142    ///     .with_recv_timeout(Duration::from_secs(10));
143    ///
144    /// let socket = ReqSocket::connect_with_options(
145    ///     "tcp://127.0.0.1:5555",
146    ///     options
147    /// ).await?;
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub async fn connect_with_options(
152        endpoint: &str,
153        options: monocoque_core::options::SocketOptions,
154    ) -> io::Result<Self> {
155        let addr = if let Ok(monocoque_core::endpoint::Endpoint::Tcp(a)) =
156            monocoque_core::endpoint::Endpoint::parse(endpoint)
157        {
158            a
159        } else {
160            endpoint
161                .parse::<std::net::SocketAddr>()
162                .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
163        };
164
165        let stream = TcpStream::connect(addr).await?;
166        let sock = Self::from_tcp_with_options(stream, options).await?;
167        sock.emit_event(SocketEvent::Connected(
168            monocoque_core::endpoint::Endpoint::Tcp(addr),
169        ));
170        Ok(sock)
171    }
172
173    /// Connect to a ZeroMQ peer via IPC (Unix domain sockets).
174    ///
175    /// Unix-only. Accepts IPC paths with or without `ipc://` prefix.
176    ///
177    /// # Example
178    ///
179    /// ```rust,no_run
180    /// # #[cfg(unix)]
181    /// # async fn example() -> std::io::Result<()> {
182    /// use monocoque::zmq::ReqSocket;
183    ///
184    /// let socket = ReqSocket::connect_ipc("/tmp/req.sock").await?;
185    /// # Ok(())
186    /// # }
187    /// ```
188    #[cfg(unix)]
189    pub async fn connect_ipc(path: &str) -> io::Result<ReqSocket<monocoque_core::rt::UnixStream>> {
190        use std::path::PathBuf;
191
192        let clean_path = path.strip_prefix("ipc://").unwrap_or(path);
193        let ipc_path = PathBuf::from(clean_path);
194
195        let stream = monocoque_core::ipc::connect(&ipc_path).await?;
196        let sock = ReqSocket::from_unix_stream(stream).await?;
197        sock.emit_event(SocketEvent::Connected(
198            monocoque_core::endpoint::Endpoint::Ipc(ipc_path),
199        ));
200        Ok(sock)
201    }
202
203    /// Create a REQ socket from a TCP stream with TCP_NODELAY enabled.
204    pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
205        Ok(Self {
206            inner: InternalReq::from_tcp(stream).await?,
207            monitor: None,
208        })
209    }
210
211    /// Create a REQ socket from a TCP stream with custom socket options.
212    pub async fn from_tcp_with_options(
213        stream: TcpStream,
214        options: monocoque_core::options::SocketOptions,
215    ) -> io::Result<Self> {
216        Ok(Self {
217            inner: InternalReq::from_tcp_with_options(stream, options).await?,
218            monitor: None,
219        })
220    }
221
222    /// Create a REQ socket from any stream with custom options.
223    pub async fn with_options<Stream>(
224        stream: Stream,
225        options: monocoque_core::options::SocketOptions,
226    ) -> io::Result<ReqSocket<Stream>>
227    where
228        Stream: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
229    {
230        Ok(ReqSocket {
231            inner: InternalReq::with_options(stream, options).await?,
232            monitor: None,
233        })
234    }
235}
236
237// Generic impl - works with any stream type
238impl<S> ReqSocket<S>
239where
240    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
241{
242    /// Enable monitoring for this socket.
243    ///
244    /// Returns a receiver for socket lifecycle events.
245    pub fn monitor(&mut self) -> SocketMonitor {
246        let (sender, receiver) = create_monitor();
247        self.monitor = Some(sender);
248        receiver
249    }
250
251    /// Helper to emit monitoring events (if monitoring is enabled).
252    fn emit_event(&self, event: SocketEvent) {
253        if let Some(monitor) = &self.monitor {
254            monocoque_core::monitor::emit(monitor, event);
255        }
256    }
257
258    /// Send a multipart message.
259    ///
260    /// This enforces the REQ state machine - you must call `recv()` before
261    /// calling `send()` again.
262    ///
263    /// # Arguments
264    ///
265    /// * `msg` - Vector of message frames (parts)
266    ///
267    /// # Errors
268    ///
269    /// Returns an error if:
270    /// - Called while awaiting a reply (must call `recv()` first)
271    /// - The underlying connection is closed
272    ///
273    /// # Example
274    ///
275    /// ```rust,no_run
276    /// use monocoque::zmq::ReqSocket;
277    /// use bytes::Bytes;
278    ///
279    /// # async fn example(socket: &mut ReqSocket) -> std::io::Result<()> {
280    /// // Send single-part message
281    /// socket.send(vec![Bytes::from("Hello")]).await?;
282    ///
283    /// // Send multi-part message
284    /// socket.send(vec![
285    ///     Bytes::from("Part 1"),
286    ///     Bytes::from("Part 2"),
287    /// ]).await?;
288    /// # Ok(())
289    /// # }
290    /// ```
291    pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
292        channel_to_io_error(self.inner.send(msg).await)
293    }
294
295    /// Get the socket type.
296    ///
297    /// # ZeroMQ Compatibility
298    ///
299    /// Corresponds to `ZMQ_TYPE` (16) option.
300    #[inline]
301    pub const fn socket_type() -> SocketType {
302        SocketType::Req
303    }
304
305    /// Get the endpoint this socket is connected/bound to, if available.
306    ///
307    /// Returns `None` if the socket was created from a raw stream.
308    ///
309    /// # ZeroMQ Compatibility
310    ///
311    /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) option.
312    #[inline]
313    pub fn last_endpoint(&self) -> Option<&monocoque_core::endpoint::Endpoint> {
314        self.inner.last_endpoint()
315    }
316
317    /// Check if the last received message has more frames coming.
318    ///
319    /// Returns `true` if there are more frames in the current multipart message.
320    ///
321    /// # ZeroMQ Compatibility
322    ///
323    /// Corresponds to `ZMQ_RCVMORE` (13) option.
324    #[inline]
325    pub fn has_more(&self) -> bool {
326        self.inner.has_more()
327    }
328
329    /// Get the event state of the socket.
330    ///
331    /// Returns a bitmask indicating ready-to-receive and ready-to-send states.
332    ///
333    /// # Returns
334    ///
335    /// - `1` (POLLIN) - Socket is ready to receive
336    /// - `2` (POLLOUT) - Socket is ready to send
337    /// - `3` (POLLIN | POLLOUT) - Socket is ready for both
338    ///
339    /// # ZeroMQ Compatibility
340    ///
341    /// Corresponds to `ZMQ_EVENTS` (15) option.
342    #[inline]
343    pub fn events(&self) -> u32 {
344        self.inner.events()
345    }
346
347    /// Receive a multipart message.
348    ///
349    /// This blocks until a reply is received. You must call this after `send()`
350    /// before calling `send()` again.
351    ///
352    /// # Returns
353    ///
354    /// - `Ok(Some(msg))` - Received a multipart message
355    /// - `Ok(None)` - Connection closed gracefully (peer disconnected)
356    /// - `Err(e)` - Network or I/O error
357    ///
358    /// # Example
359    ///
360    /// ```rust,no_run
361    /// use monocoque::zmq::ReqSocket;
362    ///
363    /// # async fn example(socket: &mut ReqSocket) -> std::io::Result<()> {
364    /// if let Some(reply) = socket.recv().await? {
365    ///     for (i, frame) in reply.iter().enumerate() {
366    ///         println!("Frame {}: {:?}", i, frame);
367    ///     }
368    /// }
369    /// # Ok(())
370    /// # }
371    /// ```
372    pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
373        self.inner.recv().await
374    }
375
376    /// Get a reference to the socket options.
377    ///
378    /// # Example
379    ///
380    /// ```rust,no_run
381    /// use monocoque::zmq::ReqSocket;
382    ///
383    /// # async fn example(socket: &ReqSocket) {
384    /// let timeout = socket.options().recv_timeout;
385    /// println!("Receive timeout: {:?}", timeout);
386    /// # }
387    /// ```
388    pub const fn options(&self) -> &monocoque_core::options::SocketOptions {
389        self.inner.options()
390    }
391
392    /// Get a mutable reference to the socket options.
393    ///
394    /// # Example
395    ///
396    /// ```rust,no_run
397    /// use monocoque::zmq::ReqSocket;
398    /// use std::time::Duration;
399    ///
400    /// # async fn example(socket: &mut ReqSocket) {
401    /// socket.options_mut().recv_timeout = Some(Duration::from_secs(30));
402    /// # }
403    /// ```
404    pub fn options_mut(&mut self) -> &mut monocoque_core::options::SocketOptions {
405        self.inner.options_mut()
406    }
407
408    /// Set the socket options.
409    ///
410    /// # Example
411    ///
412    /// ```rust,no_run
413    /// use monocoque::zmq::ReqSocket;
414    /// use monocoque::SocketOptions;
415    /// use std::time::Duration;
416    ///
417    /// # async fn example(socket: &mut ReqSocket) {
418    /// let options = SocketOptions::default()
419    ///     .with_send_timeout(Duration::from_secs(5))
420    ///     .with_recv_timeout(Duration::from_secs(10));
421    ///
422    /// socket.set_options(options);
423    /// # }
424    /// ```
425    pub fn set_options(&mut self, options: monocoque_core::options::SocketOptions) {
426        self.inner.set_options(options);
427    }
428}
429
430// Unix-specific impl for IPC support
431#[cfg(unix)]
432impl ReqSocket<monocoque_core::rt::UnixStream> {
433    /// Create a REQ socket from an existing Unix domain socket stream (IPC).
434    pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
435        Ok(Self {
436            inner: InternalReq::new(stream).await?,
437            monitor: None,
438        })
439    }
440
441    /// Create a REQ socket from an existing Unix stream with custom options.
442    ///
443    /// This method provides full control over socket behavior through SocketOptions.
444    pub async fn from_unix_stream_with_options(
445        stream: monocoque_core::rt::UnixStream,
446        options: monocoque_core::options::SocketOptions,
447    ) -> io::Result<Self> {
448        Ok(Self {
449            inner: InternalReq::with_options(stream, options).await?,
450            monitor: None,
451        })
452    }
453}