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

Code before:
                }

            // Decode from the buffer if possible
            let (decoded, ended) = try self.processor.decodeNext(
                decodeMode: readLastChunkFromBuffer ? .last : .normal,
                seenEOF: readLastChunkFromBuffer
            )

            if ended {
                // We expect `decodeNext()` to only return `ended == true` only if we've notified it
                // that we've read the last chunk from the buffer.
                assert(readLastChunkFromBuffer)
                self.state = .finishedDecoding
                return decoded
            }

            return decoded
        }

        /// Retrieve the next element from the ``NIODecodedAsyncSequence``.
        ///
        /// The same as `next(isolation:)` but not isolated to an actor, which allows
        /// for less availability restrictions.
        @inlinable
        public mutating func next() async throws -> Element? {
            switch self.state {
            case .finishedDecoding:
                return nil
            case .readingFromBuffer, .readLastChunkFromBuffer:
                break
            }

            if let decoded = try self.decodeFromBuffer() {
                return decoded
            }

            loop: while true {
                sw: switch self.state {
                case .readingFromBuffer:
                    /// Loop while we have more data to read.
                    break sw
                case .finishedDecoding, .readLastChunkFromBuffer:
                    break loop
                }

                // Read more data into the buffer so we can decode more messages
                guard let nextBuffer = try await self.baseIterator.next() else {
                    // Ran out of data to read.
                    self.state = .readLastChunkFromBuffer
                    if let decoded = try self.decodeFromBuffer() {
                        return decoded

Review comment (refactor):
I think you can simplify the state handling further: at the moment you're checking the state multiple times and it's unclear when you make a transition which state you're moving from. This is just about okay in small state machines but it very quickly gets harder to reason about than necessary.

I'd do a few things here: have `decodeFromBuffer` take the decode mode as a parameter and derive that in the main loop of `next()`. I'd also return an `enum` from there with the result of the decode. This will allow you to avoid mutating `state` in that function meaning the state will only be read and mutated in `next()`.


That way your body of `next()` can be something like:

```swift
while true {
  switch self.state {
  case .finishedDecoding:
    return nil

  case .readingFromBuffer(let isLast):
    switch self.decodeFromBuffer(mode: isLast ? .last : .normal) {
      // ...
    }
  }
}
```

Code after:
        var processor: NIOSingleStepByteToMessageProcessor<Decoder>
        @usableFromInline
        var state: State

        @inlinable
        init(base: NIODecodedAsyncSequence) {
            self.baseIterator = base.asyncSequence.makeAsyncIterator()
            self.processor = NIOSingleStepByteToMessageProcessor(
                base.decoder,
                maximumBufferSize: base.maximumBufferSize
            )
            self.state = .readingFromBuffer
        }

        /// Retrieve the next element from the ``NIODecodedAsyncSequence``.
        ///
        /// The same as `next(isolation:)` but not isolated to an actor, which allows
        /// for less availability restrictions.
        @inlinable
        public mutating func next() async throws -> Element? {
            while true {
                switch self.state {
                case .finishedDecoding:
                    return nil
                case .readingFromBuffer:
                    let (decoded, ended) = try self.processor.decodeNext(
                        decodeMode: .normal,
                        seenEOF: false
                    )

                    // We expect `decodeNext()` to only return `ended == true` only if we've notified it
                    // that we've read the last chunk from the buffer, using `decodeMode: .last`.
                    assert(!ended)

                    if let decoded {
                        return decoded
                    }

                    // Read more data into the buffer so we can decode more messages
                    guard let nextBuffer = try await self.baseIterator.next() else {
                        // Ran out of data to read.
                        self.state = .readLastChunkFromBuffer
                        continue
                    }
                    self.processor.append(nextBuffer)
                case .readLastChunkFromBuffer:
                    let (decoded, ended) = try self.processor.decodeNext(
                        decodeMode: .last,
                        seenEOF: true
                    )