Skip to main content

dnet_base/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! `dnet` base features.
5
6#[cfg(feature = "logging")]
7pub mod logging;
8#[cfg(feature = "logging")]
9pub use logging::{Logger, Logging};
10
11use std::{
12    fmt::Display,
13    io::{Read, Write},
14    pin::Pin,
15    task::{Context, Poll},
16};
17
18use futures::{
19    future::FusedFuture,
20    ready,
21    stream::{FusedStream, Next},
22    Future, FutureExt, Sink, Stream, StreamExt,
23};
24use pin_project::pin_project;
25use serde::{Deserialize, Serialize};
26
27/// Trait for encoders.
28pub trait Encode {
29    /// Error type.
30    type Error: std::error::Error;
31
32    /// Encode message into writer.
33    fn encode<W, T>(&mut self, writer: W, message: &T) -> Result<(), Self::Error>
34    where
35        W: Write,
36        T: Serialize;
37}
38
39/// Trait for decoders.
40pub trait Decode {
41    /// Error type.
42    type Error: std::error::Error;
43
44    /// Decode message from reader.
45    fn decode<R, T>(&mut self, data: R) -> Result<T, Self::Error>
46    where
47        R: Read,
48        for<'de> T: Deserialize<'de>;
49}
50
51/// Trait for `dnet` codecs.
52pub trait Codec: Encode + Decode {}
53
54impl<T> Codec for T where T: Encode + Decode {}
55
56/// Transport error.
57#[derive(Debug, PartialEq, Eq)]
58pub enum Error<Other> {
59    /// Occurs when transport is closed.
60    Closed,
61
62    /// Other non-predefined transport-specific error.
63    Other(Other),
64}
65
66impl<Other> Error<Other> {
67    /// Was error caused by transport being closed.
68    pub fn closed(&self) -> bool {
69        matches!(self, Error::Closed)
70    }
71}
72
73impl<Other> Display for Error<Other>
74where
75    Other: Display,
76{
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            Self::Closed => write!(f, "transport closed"),
80            Self::Other(other) => write!(f, "{other}"),
81        }
82    }
83}
84
85impl<Other> std::error::Error for Error<Other> where Other: std::error::Error {}
86
87/// Convenience trait for receiving messages.
88pub trait Receive<Message, Error> {
89    /// Receive message from transport.
90    fn receive(&mut self) -> Recv<'_, Self>;
91}
92
93impl<T, Message, Error> Receive<Message, Error> for T
94where
95    T: Stream<Item = Result<Message, Error>> + Unpin,
96{
97    /// Receive message from transport.
98    fn receive(&mut self) -> Recv<'_, Self> {
99        let next = self.next();
100        Recv {
101            next,
102            terminated: false,
103        }
104    }
105}
106
107/// Future returned by [receive] method.
108///
109/// [receive]: self::Receive::receive
110pub struct Recv<'a, T>
111where
112    T: ?Sized,
113{
114    next: Next<'a, T>,
115    terminated: bool,
116}
117
118impl<T, Message, Error> Future for Recv<'_, T>
119where
120    T: Stream<Item = Result<Message, Error>> + Unpin,
121{
122    type Output = Result<Message, self::Error<Error>>;
123
124    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
125        match self.next.poll_unpin(cx) {
126            Poll::Ready(item) => {
127                self.terminated = true;
128                if let Some(item) = item {
129                    Poll::Ready(item.map_err(|error| self::Error::Other(error)))
130                } else {
131                    Poll::Ready(Err(self::Error::Closed))
132                }
133            }
134            Poll::Pending => Poll::Pending,
135        }
136    }
137}
138
139impl<T, Message, Error> FusedFuture for Recv<'_, T>
140where
141    T: Stream<Item = Result<Message, Error>> + Unpin,
142{
143    fn is_terminated(&self) -> bool {
144        self.terminated
145    }
146}
147
148/// Utility trait for creating message stream with filtered-out errors.
149pub trait Messages<T, Message, Error>
150where
151    Self: Sized,
152{
153    /// Message stream with filtered-out errors.
154    ///
155    /// Calls a callback when error occurs.
156    fn messages_with_error_callback<F>(self, error_callback: F) -> MessageStream<Self, F>
157    where
158        F: FnMut(Error);
159
160    /// Message stream with filtered-out errors.
161    fn messages(self) -> MessageStream<Self, fn(Error) -> ()> {
162        self.messages_with_error_callback(|_| {})
163    }
164}
165
166impl<T, Message, Error> Messages<T, Message, Error> for T
167where
168    T: Stream<Item = Result<Message, Error>> + Unpin,
169{
170    fn messages_with_error_callback<F>(self, error_callback: F) -> MessageStream<Self, F>
171    where
172        F: FnMut(Error),
173    {
174        MessageStream {
175            stream: self,
176            error_callback,
177            terminated: false,
178        }
179    }
180}
181
182/// Stream of messages.
183///
184/// Returned by [messages] function.
185///
186/// [messages]: self::Messages::messages
187#[pin_project]
188pub struct MessageStream<T, F> {
189    #[pin]
190    stream: T,
191    error_callback: F,
192    terminated: bool,
193}
194
195impl<T, F, Message, Error> Stream for MessageStream<T, F>
196where
197    T: Stream<Item = Result<Message, Error>> + Unpin,
198    F: FnMut(Error),
199{
200    type Item = Message;
201
202    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
203        loop {
204            if let Some(result) = ready!(self.stream.poll_next_unpin(cx)) {
205                match result {
206                    Ok(message) => return Poll::Ready(Some(message)),
207                    Err(error) => {
208                        (self.error_callback)(error);
209                        continue;
210                    }
211                }
212            } else {
213                self.terminated = true;
214                return Poll::Ready(None);
215            }
216        }
217    }
218}
219
220impl<T, F, Message, Error> FusedStream for MessageStream<T, F>
221where
222    T: Stream<Item = Result<Message, Error>> + Unpin,
223    F: FnMut(Error),
224{
225    fn is_terminated(&self) -> bool {
226        self.terminated
227    }
228}
229
230#[doc(hidden)]
231/// Logging trait variant that extents `Logging` only when `logging` feature is enabled.
232pub mod conditional {
233    #[cfg(feature = "logging")]
234    /// Logging trait variant that extents `Logging` only when `logging` feature is enabled.
235    pub trait Logging: crate::logging::Logging {}
236
237    #[cfg(feature = "logging")]
238    impl<T> Logging for T where T: crate::logging::Logging {}
239
240    #[cfg(not(feature = "logging"))]
241    /// Logging trait variant that extents `Logging` only when `logging` feature is enabled.
242    pub trait Logging {}
243
244    #[cfg(not(feature = "logging"))]
245    impl<T> Logging for T {}
246}
247
248/// Trait for transports implementing `dnet` interface.
249pub trait Transport<Incoming, Outgoing, Error>:
250    Sink<Outgoing, Error = crate::Error<Error>>
251    + Stream<Item = Result<Incoming, Error>>
252    + conditional::Logging
253{
254}
255
256impl<T, Incoming, Outgoing, Error> Transport<Incoming, Outgoing, Error> for T where
257    T: Sink<Outgoing, Error = crate::Error<Error>>
258        + Stream<Item = Result<Incoming, Error>>
259        + conditional::Logging
260{
261}
262
263/// Helper trait for transports where incoming and outgoing messages are of the same type.
264pub trait SymmetricTransport<Message, Error>: Transport<Message, Message, Error> {}
265
266impl<T, Message, Error> SymmetricTransport<Message, Error> for T where
267    T: Transport<Message, Message, Error>
268{
269}