scuffle_rtmp/command_messages/
writer.rs

1//! Writing [`Command`].
2
3use std::fmt::Display;
4use std::io;
5
6use bytes::{BufMut, Bytes, BytesMut};
7
8use super::error::CommandError;
9use super::{Command, CommandResultLevel, CommandType};
10use crate::chunk::writer::ChunkWriter;
11use crate::chunk::{CHUNK_STREAM_ID_COMMAND, Chunk};
12use crate::error::RtmpError;
13use crate::messages::MessageType;
14
15impl AsRef<str> for CommandResultLevel {
16    fn as_ref(&self) -> &str {
17        match self {
18            CommandResultLevel::Warning => "warning",
19            CommandResultLevel::Status => "status",
20            CommandResultLevel::Error => "error",
21            CommandResultLevel::Unknown(s) => s,
22        }
23    }
24}
25
26impl Display for CommandResultLevel {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            CommandResultLevel::Warning => write!(f, "warning"),
30            CommandResultLevel::Status => write!(f, "status"),
31            CommandResultLevel::Error => write!(f, "error"),
32            CommandResultLevel::Unknown(s) => write!(f, "{s}"),
33        }
34    }
35}
36
37impl Command<'_> {
38    fn write_amf0_chunk(io: &mut impl io::Write, writer: &ChunkWriter, payload: Bytes) -> io::Result<()> {
39        writer.write_chunk(
40            io,
41            Chunk::new(CHUNK_STREAM_ID_COMMAND, 0, MessageType::CommandAMF0, 0, payload),
42        )
43    }
44
45    /// Writes a [`Command`] to the given writer.
46    ///
47    /// Skips unknown commands.
48    pub fn write(self, io: &mut impl io::Write, writer: &ChunkWriter) -> Result<(), RtmpError> {
49        let mut buf = BytesMut::new();
50        let mut buf_writer = (&mut buf).writer();
51
52        match self.command_type {
53            CommandType::NetConnection(command) => {
54                command.write(&mut buf_writer, self.transaction_id)?;
55            }
56            CommandType::NetStream(_) => {
57                return Err(RtmpError::from(CommandError::NoClientImplementation));
58            }
59            CommandType::OnStatus(command) => {
60                command.write(&mut buf_writer, self.transaction_id)?;
61            }
62            // don't write unknown commands
63            CommandType::Unknown { .. } => {}
64        }
65
66        Self::write_amf0_chunk(io, writer, buf.freeze())?;
67
68        Ok(())
69    }
70}
71
72#[cfg(test)]
73#[cfg_attr(all(test, coverage_nightly), coverage(off))]
74mod tests {
75    use super::super::{Command, CommandResultLevel};
76    use crate::chunk::writer::ChunkWriter;
77    use crate::command_messages::CommandType;
78    use crate::command_messages::error::CommandError;
79    use crate::command_messages::netstream::NetStreamCommand;
80    use crate::error::RtmpError;
81
82    #[test]
83    fn command_result_level_to_str() {
84        assert_eq!(CommandResultLevel::Warning.as_ref(), "warning");
85        assert_eq!(CommandResultLevel::Status.as_ref(), "status");
86        assert_eq!(CommandResultLevel::Error.as_ref(), "error");
87        assert_eq!(CommandResultLevel::Unknown("custom".to_string()).as_ref(), "custom");
88    }
89
90    #[test]
91    fn command_result_level_into_string() {
92        assert_eq!(CommandResultLevel::Warning.to_string(), "warning");
93        assert_eq!(CommandResultLevel::Status.to_string(), "status");
94        assert_eq!(CommandResultLevel::Error.to_string(), "error");
95        assert_eq!(CommandResultLevel::Unknown("custom".to_string()).to_string(), "custom");
96    }
97
98    #[test]
99    fn netstream_command_write() {
100        let mut buf = Vec::new();
101        let writer = ChunkWriter::default();
102
103        let err = Command {
104            command_type: CommandType::NetStream(NetStreamCommand::CloseStream),
105            transaction_id: 1.0,
106        }
107        .write(&mut buf, &writer)
108        .unwrap_err();
109
110        assert!(matches!(err, RtmpError::Command(CommandError::NoClientImplementation)));
111    }
112}