diff --git a/src/app.rs b/src/app.rs index 1d6a308a..a9749cc0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -147,6 +147,10 @@ where S: Serializer + Default, C: Send + 'static, { + /// Creates a new `WireframeApp` instance with default configuration. + /// + /// Initialises empty routes, services, middleware, and application data. Sets the + /// default frame processor and serializer, with no connection lifecycle hooks. fn default() -> Self { Self { routes: HashMap::new(), diff --git a/src/frame.rs b/src/frame.rs index aaa0dfab..97e31dc5 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -25,26 +25,47 @@ pub struct LengthFormat { } impl LengthFormat { - /// Create a new [`LengthFormat`]. + /// Creates a new `LengthFormat` with the specified number of bytes and + /// endianness for the length prefix. + /// + /// # Parameters + /// - `bytes`: The number of bytes used for the length prefix. + /// - `endianness`: The byte order for encoding and decoding the length prefix. + /// + /// # Returns + /// A `LengthFormat` configured with the given size and endianness. #[must_use] pub const fn new(bytes: usize, endianness: Endianness) -> Self { Self { bytes, endianness } } - /// Two byte big-endian prefix. + /// Creates a `LengthFormat` for a 2-byte big-endian length prefix. #[must_use] pub const fn u16_be() -> Self { Self::new(2, Endianness::Big) } - /// Two byte little-endian prefix. + /// Creates a `LengthFormat` for a 2-byte little-endian length prefix. #[must_use] pub const fn u16_le() -> Self { Self::new(2, Endianness::Little) } - /// Four byte big-endian prefix. + /// Creates a `LengthFormat` for a 4-byte big-endian length prefix. #[must_use] pub const fn u32_be() -> Self { Self::new(4, Endianness::Big) } - /// Four byte little-endian prefix. + /// Creates a `LengthFormat` for a 4-byte little-endian length prefix. #[must_use] pub const fn u32_le() -> Self { Self::new(4, Endianness::Little) } + /// Reads a length prefix from a byte slice according to the configured prefix size and + /// endianness. + /// + /// # Parameters + /// - `bytes`: The byte slice containing the length prefix. Must be at least as long as the + /// configured prefix size. + /// + /// # Returns + /// The decoded length as a `usize` if successful. + /// + /// # Errors + /// Returns an error if the prefix size is unsupported or if the decoded length does not fit in + /// a `usize`. fn read_len(&self, bytes: &[u8]) -> io::Result { let len = match (self.bytes, self.endianness) { (1, _) => u64::from(u8::from_ne_bytes([bytes[0]])), @@ -72,6 +93,18 @@ impl LengthFormat { usize::try_from(len).map_err(|_| io::Error::other("frame too large")) } + /// Writes a length prefix to the destination buffer using the configured size and endianness. + /// + /// Returns an error if the length is too large to fit in the configured prefix size or if the + /// prefix size is unsupported. + /// + /// # Parameters + /// - `len`: The length value to encode and write. + /// - `dst`: The buffer to which the encoded length prefix will be appended. + /// + /// # Errors + /// Returns an error if `len` exceeds the maximum value for the configured prefix size or if the + /// prefix size is not supported. fn write_len(&self, len: usize, dst: &mut BytesMut) -> io::Result<()> { match (self.bytes, self.endianness) { (1, _) => dst.put_u8( @@ -120,6 +153,9 @@ impl LengthFormat { } impl Default for LengthFormat { + /// Returns a `LengthFormat` using a 4-byte big-endian length prefix. + /// + /// This is the default format for length-prefixed framing. fn default() -> Self { Self::u32_be() } } @@ -159,12 +195,24 @@ pub struct LengthPrefixedProcessor { } impl LengthPrefixedProcessor { - /// Construct a processor with the provided [`LengthFormat`]. + /// Creates a new `LengthPrefixedProcessor` with the specified length prefix + /// format. + /// + /// # Parameters + /// - `format`: The length prefix format to use for framing. + /// + /// # Returns + /// A `LengthPrefixedProcessor` configured with the given length format. #[must_use] pub const fn new(format: LengthFormat) -> Self { Self { format } } } impl Default for LengthPrefixedProcessor { + /// Creates a `LengthPrefixedProcessor` using the default length format (4-byte big-endian + /// prefix). + /// + /// # Returns + /// A processor configured for 4-byte big-endian length-prefixed framing. fn default() -> Self { Self::new(LengthFormat::default()) } } @@ -172,6 +220,13 @@ impl FrameProcessor for LengthPrefixedProcessor { type Frame = Vec; type Error = std::io::Error; + /// Attempts to decode a single length-prefixed frame from the source buffer. + /// + /// Returns `Ok(Some(frame))` if a complete frame is available, `Ok(None)` if + /// more data is needed, or an error if the length prefix is invalid or cannot + /// be read according to the configured format. + /// + /// The source buffer is advanced past the decoded frame and its length prefix. fn decode(&self, src: &mut BytesMut) -> Result, Self::Error> { if src.len() < self.format.bytes { return Ok(None); @@ -184,6 +239,10 @@ impl FrameProcessor for LengthPrefixedProcessor { Ok(Some(src.split_to(len).to_vec())) } + /// Encodes a frame by prefixing it with its length and appending it to the destination buffer. + /// + /// The length prefix format is determined by the processor's configuration. Returns an error + /// if the frame length cannot be represented in the configured format. fn encode(&self, frame: &Self::Frame, dst: &mut BytesMut) -> Result<(), Self::Error> { dst.reserve(self.format.bytes + frame.len()); self.format.write_len(frame.len(), dst)?; diff --git a/tests/response.rs b/tests/response.rs index b90f6988..7b024e81 100644 --- a/tests/response.rs +++ b/tests/response.rs @@ -35,6 +35,8 @@ impl<'de> bincode::BorrowDecode<'de, ()> for FailingResp { } #[tokio::test] +/// Tests that sending a response serialises and frames the data correctly, +/// and that the response can be decoded and deserialised back to its original value asynchronously. async fn send_response_encodes_and_frames() { let app = WireframeApp::new() .unwrap() @@ -52,6 +54,9 @@ async fn send_response_encodes_and_frames() { } #[tokio::test] +/// Tests that decoding with an incomplete length prefix header returns `None` and does not consume any bytes from the buffer. +/// +/// This ensures that the decoder waits for the full header before attempting to decode a frame. async fn length_prefixed_decode_requires_complete_header() { let processor = LengthPrefixedProcessor::default(); let mut buf = BytesMut::from(&[0x00, 0x00, 0x00][..]); // only 3 bytes @@ -60,6 +65,10 @@ async fn length_prefixed_decode_requires_complete_header() { } #[tokio::test] +/// Tests that decoding with a complete length prefix but incomplete frame data returns `None` +/// and retains all bytes in the buffer. +/// +/// Ensures that the decoder does not consume any bytes when the full frame is not yet available. async fn length_prefixed_decode_requires_full_frame() { let processor = LengthPrefixedProcessor::default(); let mut buf = BytesMut::from(&[0x00, 0x00, 0x00, 0x05, 0x01, 0x02][..]); @@ -95,6 +104,10 @@ impl tokio::io::AsyncWrite for FailingWriter { } #[tokio::test] +/// Tests that `send_response` correctly propagates I/O errors encountered during writing. +/// +/// This test uses a writer that always fails on write operations and asserts that +/// the resulting error is of the `Io` variant. async fn send_response_propagates_write_error() { let app = WireframeApp::new() .unwrap() @@ -109,6 +122,10 @@ async fn send_response_propagates_write_error() { } #[tokio::test] +/// Tests that `send_response` returns a serialization error when encoding fails. +/// +/// This test sends a `FailingResp` using `send_response` and asserts that the resulting +/// error is of the `Serialize` variant, indicating a failure during response encoding. async fn send_response_returns_encode_error() { let app = WireframeApp::new().unwrap(); let err = app @@ -119,6 +136,10 @@ async fn send_response_returns_encode_error() { } #[test] +/// Tests roundtrip encoding and decoding of a frame using a two-byte big-endian length prefix. +/// +/// Verifies that a frame encoded with a `LengthPrefixedProcessor` configured for a 2-byte +/// big-endian length format can be correctly decoded back to its original contents. fn custom_two_byte_big_endian_roundtrip() { let fmt = LengthFormat::u16_be(); let processor = LengthPrefixedProcessor::new(fmt); @@ -130,6 +151,10 @@ fn custom_two_byte_big_endian_roundtrip() { } #[test] +/// Tests roundtrip encoding and decoding of a frame using a four-byte little-endian length prefix. +/// +/// Verifies that the encoded buffer contains the correct little-endian length prefix and that +/// decoding restores the original frame. fn custom_four_byte_little_endian_roundtrip() { let fmt = LengthFormat::u32_le(); let processor = LengthPrefixedProcessor::new(fmt);