Repository: apple/swift-nio
File: Sources/NIOPosix/Bootstrap.swift (Swift)

Code before:
    ///         when the `Channel` becomes inactive. You _must not_ do any further operations `inputDescriptor` or
    ///         `outputDescriptor`, including `close`.
    ///         If this method throws, you still own the file descriptors and are responsible for
    ///         closing them.
    ///
    /// - Parameters:
    ///   - inputDescriptor: The _Unix file descriptor_ for the input (ie. the read side).
    ///   - outputDescriptor: The _Unix file descriptor_ for the output (ie. the write side).
    ///   - channelInitializer: A closure to initialize the channel which must return the handler that is used for negotiating
    ///   the protocol.
    /// - Returns: The protocol negotiation result.
    @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
    @_spi(AsyncChannel)
    public func withPipes<Handler: NIOProtocolNegotiationHandler>(
        inputDescriptor: CInt,
        outputDescriptor: CInt,
        channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<Handler>
    ) async throws -> Handler.NegotiationResult {
        precondition(inputDescriptor >= 0 && outputDescriptor >= 0 && inputDescriptor != outputDescriptor,
                     "illegal file descriptor pair. The file descriptors \(inputDescriptor), \(outputDescriptor) " +
                     "must be distinct and both positive integers.")
        let eventLoop = group.next()
        try self.validateFileDescriptorIsNotAFile(inputDescriptor)
        try self.validateFileDescriptorIsNotAFile(outputDescriptor)

        let channelInitializer = { (channel: Channel) -> EventLoopFuture<Handler> in
            let initializer = self.channelInitializer ?? { _ in eventLoop.makeSucceededFuture(()) }
            return initializer(channel).flatMap { channelInitializer(channel) }
        }

        let inputFileHandle = NIOFileHandle(descriptor: inputDescriptor)
        let outputFileHandle = NIOFileHandle(descriptor: outputDescriptor)
        let channel = try PipeChannel(
            eventLoop: eventLoop as! SelectableEventLoop,
            inputPipe: inputFileHandle,
            outputPipe: outputFileHandle
        )

        @Sendable
        func setupChannel() -> EventLoopFuture<Handler.NegotiationResult> {
            eventLoop.assertInEventLoop()
            return self._channelOptions.applyAllChannelOptions(to: channel).flatMap {
                channelInitializer(channel)
            }.flatMap { handler in
                eventLoop.assertInEventLoop()
                let promise = eventLoop.makePromise(of: Void.self)
                channel.registerAlreadyConfigured0(promise: promise)
                return promise.futureResult.map { handler }
            }.flatMapError { error in
                channel.close0(error: error, mode: .all, promise: nil)
                return channel.eventLoop.makeFailedFuture(error)

Review comment (suggestion):
Can the body of this be written in terms of the variant that returns `Output`? There's a lot of duplication.

Code after:
    
    /// Create the `PipeChannel` with the provided input and output file descriptors.
    ///
    /// The input and output file descriptors must be distinct. If you have a single file descriptor, consider using
    /// `ClientBootstrap.withConnectedSocket(descriptor:)` if it's a socket or
    /// `NIOPipeBootstrap.takingOwnershipOfDescriptor` if it is not a socket.
    ///
    /// - Note: If this method returns a succeeded future, SwiftNIO will close `input` and `output`
    ///         when the `Channel` becomes inactive. You _must not_ do any further operations `input` or
    ///         `output`, including `close`.
    ///         If this method returns a failed future, you still own the file descriptors and are responsible for
    ///         closing them.
    ///
    /// - Parameters:
    ///   - input: The _Unix file descriptor_ for the input (ie. the read side).
    ///   - output: The _Unix file descriptor_ for the output (ie. the write side).
    ///   - channelInitializer: A closure to initialize the channel which must return the handler that is used for negotiating
    ///   the protocol.
    /// - Returns: The protocol negotiation result.
    @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
    @_spi(AsyncChannel)
    public func takingOwnershipOfDescriptors<Handler: NIOProtocolNegotiationHandler>(
        input: CInt,
        output: CInt,
        channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<Handler>
    ) async throws -> Handler.NegotiationResult {
        try await self._takingOwnershipOfDescriptors(
            input: input,
            output: output,
            channelInitializer: channelInitializer,
            postRegisterTransformation: { eventLoop, handler in
                return handler.protocolNegotiationResult.flatMap { result in
                    result.resolve(on: eventLoop)
                }
            }
        )
    }
}

// MARK: Arbitrary payload

extension NIOPipeBootstrap {
    /// Create the `PipeChannel` with the provided file descriptor which is used for both input & output.
    ///
    /// This method is useful for specialilsed use-cases where you want to use `NIOPipeBootstrap` for say a serial line.
    ///
    /// - Note: If this method returns a succeeded future, SwiftNIO will close `inputOutput` when the `Channel`
    ///         becomes inactive. You _must not_ do any further operations with `inputOutput`, including `close`.
    ///         If this method returns a failed future, you still own the file descriptor and are responsible for
    ///         closing it.
    ///