Repository: apple/swift-nio
File: Sources/NIOCore/SingleStepByteToMessageDecoder.swift (Swift)

Code before:
        }

        // buffer can only be nil if we're called from finishProcessing which is handled above
        assert(self._buffer != nil)

        func decodeOnce(buffer: inout ByteBuffer) throws -> Decoder.InboundOut? {
            if decodeMode == .normal {
                return try self.decoder.decode(buffer: &buffer)
            } else {
                return try self.decoder.decodeLast(buffer: &buffer, seenEOF: seenEOF)
            }
        }

        while let message = try self._withNonCoWBuffer(decodeOnce) {
            try messageReceiver(message)
        }

        if let maximumBufferSize = self.maximumBufferSize, self._buffer!.readableBytes > maximumBufferSize {
            throw ByteToMessageDecoderError.PayloadTooLargeError()
        }

        // force unwrapping is safe because nil case is handled already and asserted above
        if let readerIndex = self._buffer?.readerIndex, readerIndex > 0, self.decoder.shouldReclaimBytes(buffer: self._buffer!) {
            self._buffer!.discardReadBytes()
        }
    }
}

@available(*, unavailable)
extension NIOSingleStepByteToMessageProcessor: Sendable {}

// MARK: NIOSingleStepByteToMessageProcessor Public API
extension NIOSingleStepByteToMessageProcessor {
    /// The number of bytes that are currently not processed by the ``process(buffer:_:)`` method. Having unprocessed
    /// bytes may result from receiving only partial messages or from receiving multiple messages at once.
    public var unprocessedBytes: Int {
        self._buffer?.readableBytes ?? 0
    }

    /// Feed data into the `NIOSingleStepByteToMessageProcessor`
    ///
    /// - parameters:
    ///     - buffer: The `ByteBuffer` containing the next data in the stream
    ///     - messageReceiver: A closure called for each message produced by the `Decoder`
    @inlinable
    public func process(buffer: ByteBuffer, _ messageReceiver: (Decoder.InboundOut) throws -> Void) throws {
        self._append(buffer)
        try self._decodeLoop(decodeMode: .normal, messageReceiver)
    }

    /// Call when there is no data left in the stream. Calls `Decoder`.`decodeLast` one or more times. If there is no data left

Review comment (suggestion):
Very minor nit, but we can avoid the other force unwraps if we assign the `buffer` rather than the reader index:
```suggestion
        if let buffer = self._buffer, buffer.readerIndex > 0, self.decoder.shouldReclaimBytes(buffer: buffer) {
            buffer.discardReadBytes()
```

Code after:
            return
        }

        // buffer can only be nil if we're called from finishProcessing which is handled above
        assert(self._buffer != nil)

        func decodeOnce(buffer: inout ByteBuffer) throws -> Decoder.InboundOut? {
            if decodeMode == .normal {
                return try self.decoder.decode(buffer: &buffer)
            } else {
                return try self.decoder.decodeLast(buffer: &buffer, seenEOF: seenEOF)
            }
        }

        while let message = try self._withNonCoWBuffer(decodeOnce) {
            try messageReceiver(message)
        }

        if let maximumBufferSize = self.maximumBufferSize, self._buffer!.readableBytes > maximumBufferSize {
            throw ByteToMessageDecoderError.PayloadTooLargeError()
        }

        if let readerIndex = self._buffer?.readerIndex, readerIndex > 0,
            self.decoder.shouldReclaimBytes(buffer: self._buffer!)
        {
            self._buffer!.discardReadBytes()
        }
    }
}

@available(*, unavailable)
extension NIOSingleStepByteToMessageProcessor: Sendable {}

// MARK: NIOSingleStepByteToMessageProcessor Public API
extension NIOSingleStepByteToMessageProcessor {
    /// The number of bytes that are currently not processed by the ``process(buffer:_:)`` method. Having unprocessed
    /// bytes may result from receiving only partial messages or from receiving multiple messages at once.
    public var unprocessedBytes: Int {
        self._buffer?.readableBytes ?? 0
    }

    /// Feed data into the `NIOSingleStepByteToMessageProcessor`
    ///
    /// - parameters:
    ///     - buffer: The `ByteBuffer` containing the next data in the stream
    ///     - messageReceiver: A closure called for each message produced by the `Decoder`
    @inlinable
    public func process(buffer: ByteBuffer, _ messageReceiver: (Decoder.InboundOut) throws -> Void) throws {
        self._append(buffer)
        try self._decodeLoop(decodeMode: .normal, messageReceiver)
    }