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

Code before:
/// A handler which turns a given `MessageToByteEncoder` into a `ChannelOutboundHandler` that can then be added to a
/// `ChannelPipeline`.
public final class MessageToByteHandler<Encoder: MessageToByteEncoder>: ChannelOutboundHandler {
    public typealias OutboundOut = ByteBuffer
    public typealias OutboundIn = Encoder.OutboundIn

    private enum State {
        case notInChannelYet
        case operational
        case error(Error)
        case done

        var readyToBeAddedToChannel: Bool {
            switch self {
            case .notInChannelYet:
                return true
            case .operational, .error, .done:
                return false
            }
        }
    }

    private var state: State = .notInChannelYet
    private let encoder: Encoder
    private var buffer: ByteBuffer? = nil
    private let maxBufferCapacity: Int

    public init(_ encoder: Encoder, maxBufferCapacity: Int = 2048) {
        self.encoder = encoder
        self.maxBufferCapacity = maxBufferCapacity
    }
}

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

extension MessageToByteHandler {
    public func handlerAdded(context: ChannelHandlerContext) {
        precondition(self.state.readyToBeAddedToChannel,
                     "illegal state when adding to Channel: \(self.state)")
        self.state = .operational
        self.buffer = context.channel.allocator.buffer(capacity: 256)
    }

    public func handlerRemoved(context: ChannelHandlerContext) {
        self.state = .done
        self.buffer = nil
    }

    public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
        switch self.state {

Review comment (suggestion):
I toyed around the idea of having the ability to switch between different strategies, for example a median of the last N number of write operations and using that as the size to clamp around. I think if there is consistently large requests, this clamping would be more pain than it would be useful. But I don't fully understand the context of the repo so I let it be. LMK your thoughts. 

Code after:
}

extension ByteToMessageHandler: RemovableChannelHandler {
    public func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
        precondition(self.removalState == .notBeingRemoved)
        self.removalState = .removalStarted
        context.eventLoop.assumeIsolated().execute {
            self.processLeftovers(context: context)
            assert(!self.state.isLeftoversNeedProcessing, "illegal state: \(self.state)")
            switch self.removalState {
            case .removalStarted:
                self.removalState = .removalCompleted
            case .handlerRemovedCalled:
                // if we're here, then the channel has also been torn down between the start and the completion of
                // the user-triggered removal. That's okay.
                ()
            default:
                assertionFailure("illegal removal state: \(self.removalState)")
            }
            // this is necessary as it'll complete the promise.
            context.leavePipeline(removalToken: removalToken)
        }
    }
}

/// A handler which turns a given `MessageToByteEncoder` into a `ChannelOutboundHandler` that can then be added to a
/// `ChannelPipeline`.
public final class MessageToByteHandler<Encoder: MessageToByteEncoder>: ChannelOutboundHandler {
    public typealias OutboundOut = ByteBuffer
    public typealias OutboundIn = Encoder.OutboundIn

    private enum State {
        case notInChannelYet
        case operational
        case error(Error)
        case done

        var readyToBeAddedToChannel: Bool {
            switch self {
            case .notInChannelYet:
                return true
            case .operational, .error, .done:
                return false
            }
        }
    }

    private var state: State = .notInChannelYet
    private let encoder: Encoder
    private var buffer: ByteBuffer? = nil
    private let desiredBufferCapacity: Int?