1use crate::chunk::error::ChunkReadError;
4use crate::command_messages::error::CommandError;
5use crate::handshake::complex::error::ComplexHandshakeError;
6use crate::session::server::ServerSessionError;
7
8#[derive(Debug, thiserror::Error)]
10pub enum RtmpError {
11 #[error("io error: {0}")]
13 Io(#[from] std::io::Error),
14 #[error("chunk read error: {0}")]
16 ChunkRead(#[from] ChunkReadError),
17 #[error("command error: {0}")]
19 Command(#[from] CommandError),
20 #[error("complex handshake error: {0}")]
22 ComplexHandshake(#[from] ComplexHandshakeError),
23 #[error("session error: {0}")]
25 Session(#[from] ServerSessionError),
26}
27
28impl RtmpError {
29 pub fn is_client_closed(&self) -> bool {
31 match self {
32 Self::Io(err) => matches!(
33 err.kind(),
34 std::io::ErrorKind::ConnectionAborted
35 | std::io::ErrorKind::ConnectionReset
36 | std::io::ErrorKind::UnexpectedEof
37 ),
38 Self::Session(ServerSessionError::Timeout(_)) => true,
39 _ => false,
40 }
41 }
42}
43
44#[cfg(test)]
45#[cfg_attr(all(test, coverage_nightly), coverage(off))]
46mod tests {
47 use std::future;
48 use std::io::ErrorKind;
49 use std::time::Duration;
50
51 use crate::error::RtmpError;
52 use crate::session::server::ServerSessionError;
53
54 #[tokio::test]
55 async fn test_is_client_closed() {
56 assert!(RtmpError::Io(std::io::Error::new(ErrorKind::ConnectionAborted, "test")).is_client_closed());
57 assert!(RtmpError::Io(std::io::Error::new(ErrorKind::ConnectionReset, "test")).is_client_closed());
58 assert!(RtmpError::Io(std::io::Error::new(ErrorKind::UnexpectedEof, "test")).is_client_closed());
59
60 let elapsed = tokio::time::timeout(Duration::ZERO, future::pending::<()>())
61 .await
62 .unwrap_err();
63
64 assert!(RtmpError::Session(ServerSessionError::Timeout(elapsed)).is_client_closed());
65
66 assert!(!RtmpError::Io(std::io::Error::other("test")).is_client_closed());
67 }
68}