Copying my suggestion from the original thread.
Consider the following "indirect" design for the protocols:
enum InputMethod {
case pipe((StandardInputWriter) async throws -> Void)
case fileDescriptor(FileDescriptor, closeAfterSpawning: Bool)
case devNull
}
public protocol InputProtocol: Sendable {
var inputMethod: InputMethod { get }
}
enum OutputMethod<T: Sendable> {
case collect(maxSize: Int, (RawSpan) throws -> T)
case fileDescriptor(FileDescriptor, closeAfterSpawning: Bool)
case stream
case discard
}
public protocol OutputProtocol: Sendable {
associatedtype OutputType: Sendable
var outputMethod: OutputMethod<OutputType> { get }
}
The conformances
struct NoInput: InputProtocol {
var inputMethod: InputMethod { .devNull }
}
struct FileDescriptorInput: InputProtocol {
let fd: FileDescriptor
let closeAfterSpawning: Bool
var inputMethod: InputMethod {
.fileDescriptor(fd, closeAfterSpawning: closeAfterSpawning)
}
}
struct StringInput: InputProtocol {
let string: String
var inputMethod: InputMethod {
.pipe { writer in
try await writer.write(string.utf8)
try await writer.finish()
}
}
}
struct ArrayInput: InputProtocol {
let array: [UInt8]
var inputMethod: InputMethod {
.pipe { writer in
try await writer.write(array)
try await writer.finish()
}
}
}
struct DiscardedOutput: OutputProtocol {
typealias OutputType = Void
var outputMethod: OutputMethod<Void> { .discard }
}
struct FileDescriptorOutput: OutputProtocol {
let fd: FileDescriptor
let closeAfterSpawning: Bool
typealias OutputType = Void
var outputMethod: OutputMethod<Void> {
.fileDescriptor(fd, closeAfterSpawning: closeAfterSpawning)
}
}
struct StringOutput: OutputProtocol {
let limit: Int
let encoding: any Encoding.Type
typealias OutputType = String
var outputMethod: OutputMethod<String> {
.collect(maxSize: limit) { String(decoding: $0, as: encoding) }
}
}
struct BytesOutput: OutputProtocol {
let limit: Int
typealias OutputType = [UInt8]
var outputMethod: OutputMethod<[UInt8]> {
.collect(maxSize: limit) { Array($0) }
}
}
struct SequenceOutput: OutputProtocol {
typealias OutputType = Void
var outputMethod: OutputMethod<Void> { .stream }
}
Changes to run method
switch input.inputMethod {
case .devNull:
// redirect to /dev/null
case .fileDescriptor(let fd, let close):
// use fd directly
case .pipe(let writeFn):
// create pipe, call writeFn(writer)
}