From 62c74f96b5c911af7e73be02f2fec33f1751dffe Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Thu, 16 Jul 2026 19:31:53 +0200 Subject: [PATCH] Adopt MultipartKit --- Package.swift | 4 +- .../Conversion/CurrencyExtensions.swift | 29 +- .../MultipartBytesToFramesSequence.swift | 407 ------------------ .../MultipartFramesToBytesSequence.swift | 321 -------------- .../MultipartFramesToRawPartsSequence.swift | 398 ----------------- .../Multipart/MultipartInternalTypes.swift | 26 -- .../MultipartRawPartsToFramesSequence.swift | 223 ---------- .../Test_MultipartBytesToFramesSequence.swift | 186 -------- .../Test_MultipartFramesToBytesSequence.swift | 104 ----- ...st_MultipartFramesToRawPartsSequence.swift | 138 ------ ...st_MultipartRawPartsToFramesSequence.swift | 150 ------- Tests/OpenAPIRuntimeTests/Test_Runtime.swift | 9 +- 12 files changed, 31 insertions(+), 1964 deletions(-) delete mode 100644 Sources/OpenAPIRuntime/Multipart/MultipartBytesToFramesSequence.swift delete mode 100644 Sources/OpenAPIRuntime/Multipart/MultipartFramesToBytesSequence.swift delete mode 100644 Sources/OpenAPIRuntime/Multipart/MultipartFramesToRawPartsSequence.swift delete mode 100644 Sources/OpenAPIRuntime/Multipart/MultipartInternalTypes.swift delete mode 100644 Sources/OpenAPIRuntime/Multipart/MultipartRawPartsToFramesSequence.swift delete mode 100644 Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartBytesToFramesSequence.swift delete mode 100644 Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToBytesSequence.swift delete mode 100644 Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToRawPartsSequence.swift delete mode 100644 Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartRawPartsToFramesSequence.swift diff --git a/Package.swift b/Package.swift index 60f04bb3..7afa27b0 100644 --- a/Package.swift +++ b/Package.swift @@ -31,12 +31,14 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-http-types", from: "1.0.0"), + .package(url: "https://github.com/vapor/multipart-kit.git", branch: "stream-multipart-part"), ], targets: [ .target( name: "OpenAPIRuntime", dependencies: [ - .product(name: "HTTPTypes", package: "swift-http-types") + .product(name: "HTTPTypes", package: "swift-http-types"), + .product(name: "MultipartKit", package: "multipart-kit"), ] ), .testTarget( diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 12004485..b587418e 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -18,6 +18,7 @@ import FoundationEssentials import Foundation #endif import HTTPTypes +import MultipartKit extension ParameterStyle { @@ -233,9 +234,14 @@ extension Converter { return untypedPart } let validated = MultipartValidationSequence(upstream: untyped, requirements: requirements) - let frames = MultipartRawPartsToFramesSequence(upstream: validated) - let bytes = MultipartFramesToBytesSequence(upstream: frames, boundary: boundary) - return HTTPBody(bytes, length: .unknown, iterationBehavior: multipart.iterationBehavior) + let parts = validated.map { StreamingMultipartPart(headerFields: $0.headerFields, body: $0.body) } + let sections = StreamingMultipartSectionAsyncSequence(parts: parts) + let writer = StreamingMultipartWriterAsyncSequence( + backingSequence: sections, + boundary: boundary, + outboundBody: ArraySlice.self + ) + return HTTPBody(writer, length: .unknown, iterationBehavior: multipart.iterationBehavior) } /// Returns a parsed multipart body. @@ -251,8 +257,21 @@ extension Converter { requirements: MultipartBodyRequirements, transform: @escaping @Sendable (MultipartRawPart) async throws -> Part ) -> MultipartBody { - let frames = MultipartBytesToFramesSequence(upstream: bytes, boundary: boundary) - let raw = MultipartFramesToRawPartsSequence(upstream: frames) + let sections = StreamingMultipartParserAsyncSequence(boundary: boundary, buffer: bytes) + let parts = StreamingMultipartPartAsyncSequence(backingSequence: sections) + let raw = parts.map { part in + let length: HTTPBody.Length + if let contentLength = part.headerFields[.contentLength], let byteCount = Int64(contentLength) { + length = .known(byteCount) + } else { + length = .unknown + } + // The body is a view over the shared parser iterator, so it can only be iterated once. + return MultipartRawPart( + headerFields: part.headerFields, + body: HTTPBody(part.body, length: length, iterationBehavior: .single) + ) + } let validated = MultipartValidationSequence(upstream: raw, requirements: requirements) let typed = validated.map(transform) return .init(typed, iterationBehavior: bytes.iterationBehavior) diff --git a/Sources/OpenAPIRuntime/Multipart/MultipartBytesToFramesSequence.swift b/Sources/OpenAPIRuntime/Multipart/MultipartBytesToFramesSequence.swift deleted file mode 100644 index 8155cffb..00000000 --- a/Sources/OpenAPIRuntime/Multipart/MultipartBytesToFramesSequence.swift +++ /dev/null @@ -1,407 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import HTTPTypes - -/// A sequence that parses multipart frames from bytes. -struct MultipartBytesToFramesSequence: Sendable -where Upstream.Element == ArraySlice { - - /// The source of byte chunks. - var upstream: Upstream - - /// The boundary string used to separate multipart parts. - var boundary: String -} - -extension MultipartBytesToFramesSequence: AsyncSequence { - - /// The type of element produced by this asynchronous sequence. - typealias Element = MultipartFrame - - /// Creates the asynchronous iterator that produces elements of this - /// asynchronous sequence. - /// - /// - Returns: An instance of the `AsyncIterator` type used to produce - /// elements of the asynchronous sequence. - func makeAsyncIterator() -> Iterator { - Iterator(upstream: upstream.makeAsyncIterator(), boundary: boundary) - } - - /// An iterator that pulls byte chunks from the upstream iterator and provides - /// parsed multipart frames. - struct Iterator: AsyncIteratorProtocol - where UpstreamIterator.Element == ArraySlice { - /// The iterator that provides the byte chunks. - private var upstream: UpstreamIterator - - /// The multipart frame parser. - private var parser: MultipartParser - /// Creates a new iterator from the provided source of byte chunks and a boundary string. - /// - Parameters: - /// - upstream: The iterator that provides the byte chunks. - /// - boundary: The boundary separating the multipart parts. - init(upstream: UpstreamIterator, boundary: String) { - self.upstream = upstream - self.parser = .init(boundary: boundary) - } - - /// Asynchronously advances to the next element and returns it, or ends the - /// sequence if there is no next element. - /// - /// - Returns: The next element, if it exists, or `nil` to signal the end of - /// the sequence. - mutating func next() async throws -> MultipartFrame? { try await parser.next { try await upstream.next() } } - } -} - -/// A parser of multipart frames from bytes. -struct MultipartParser { - - /// The underlying state machine. - private var stateMachine: StateMachine - - /// Creates a new parser. - /// - Parameter boundary: The boundary that separates parts. - init(boundary: String) { self.stateMachine = .init(boundary: boundary) } - - /// Parses the next frame. - /// - Parameter fetchChunk: A closure that is called when the parser - /// needs more bytes to parse the next frame. - /// - Returns: A parsed frame, or nil at the end of the message. - /// - Throws: When a parsing error is encountered. - mutating func next(_ fetchChunk: () async throws -> ArraySlice?) async throws -> MultipartFrame? { - while true { - switch stateMachine.readNextPart() { - case .none: continue - case .emitError(let actionError): throw ParserError(error: actionError) - case .returnNil: return nil - case .emitHeaderFields(let httpFields): return .headerFields(httpFields) - case .emitBodyChunk(let bodyChunk): return .bodyChunk(bodyChunk) - case .needsMore: - let chunk = try await fetchChunk() - switch stateMachine.receivedChunk(chunk) { - case .none: continue - case .returnNil: return nil - case .emitError(let actionError): throw ParserError(error: actionError) - } - } - } - } -} - -extension MultipartParser { - - /// An error thrown by the parser. - struct ParserError: Swift.Error, CustomStringConvertible, LocalizedError { - - /// The underlying error emitted by the state machine. - let error: MultipartParser.StateMachine.ActionError - - var description: String { - switch error { - case .invalidInitialBoundary: return "Invalid initial boundary." - case .invalidCRLFAtStartOfHeaderField: return "Invalid CRLF at the start of a header field." - case .missingColonAfterHeaderName: return "Missing colon after header field name." - case .invalidCharactersInHeaderFieldName: return "Invalid characters in a header field name." - case .incompleteMultipartMessage: return "Incomplete multipart message." - case .receivedChunkWhenFinished: return "Received a chunk after being finished." - } - } - - var errorDescription: String? { description } - } -} - -extension MultipartParser { - - /// A state machine representing the byte to multipart frame parser. - struct StateMachine { - - /// The possible states of the state machine. - enum State: Hashable { - - /// Has not yet fully parsed the initial boundary. - case parsingInitialBoundary([UInt8]) - - /// A substate when parsing a part. - enum PartState: Hashable { - - /// Accumulating part headers. - case parsingHeaderFields(HTTPFields) - - /// Forwarding body chunks. - case parsingBody - } - - /// Is parsing a part. - case parsingPart([UInt8], PartState) - - /// Finished, the terminal state. - case finished - - /// Helper state to avoid copy-on-write copies. - case mutating - } - - /// The current state of the state machine. - private(set) var state: State - - /// The bytes of the boundary. - private let boundary: ArraySlice - - /// The bytes of the boundary with the double dash prepended. - private let dashDashBoundary: ArraySlice - - /// The bytes of the boundary prepended by CRLF + double dash. - private let crlfDashDashBoundary: ArraySlice - - /// Creates a new state machine. - /// - Parameter boundary: The boundary used to separate parts. - init(boundary: String) { - self.state = .parsingInitialBoundary([]) - self.boundary = ArraySlice(boundary.utf8) - self.dashDashBoundary = ASCII.dashes + self.boundary - self.crlfDashDashBoundary = ASCII.crlf + dashDashBoundary - } - - /// An error returned by the state machine. - enum ActionError: Hashable { - - /// The initial boundary is malformed. - case invalidInitialBoundary - - /// The expected CRLF at the start of a header is missing. - case invalidCRLFAtStartOfHeaderField - - /// A header field name contains an invalid character. - case invalidCharactersInHeaderFieldName - - /// The header field name is not followed by a colon. - case missingColonAfterHeaderName - - /// More bytes were received after completion. - case receivedChunkWhenFinished - - /// Ran out of bytes without the message being complete. - case incompleteMultipartMessage - } - - /// An action returned by the `readNextPart` method. - enum ReadNextPartAction: Hashable { - - /// No action, call `readNextPart` again. - case none - - /// Throw the provided error. - case emitError(ActionError) - - /// Return nil to the caller, no more frames. - case returnNil - - /// Emit a frame with the provided header fields. - case emitHeaderFields(HTTPFields) - - /// Emit a frame with the provided part body chunk. - case emitBodyChunk(ArraySlice) - - /// Needs more bytes to parse the next frame. - case needsMore - } - - /// Read the next frame from the accumulated bytes. - /// - Returns: An action to perform. - mutating func readNextPart() -> ReadNextPartAction { - switch state { - case .mutating: preconditionFailure("Invalid state: \(state)") - case .finished: return .returnNil - case .parsingInitialBoundary(var buffer): - state = .mutating - // These first bytes must be the boundary already, otherwise this is a malformed multipart body. - switch buffer.firstIndexAfterPrefix(dashDashBoundary) { - case .index(let index): - buffer.removeSubrange(buffer.startIndex...Index - switch buffer.firstIndexAfterPrefix(ASCII.crlf) { - case .index(let index): indexAfterFirstCRLF = index - case .reachedEndOfSelf: - state = .parsingPart(buffer, .parsingHeaderFields(headerFields)) - return .needsMore - case .unexpectedPrefix: - state = .finished - return .emitError(.invalidCRLFAtStartOfHeaderField) - } - // If CRLF is here, this is the end of header fields section. - switch buffer[indexAfterFirstCRLF...].firstIndexAfterPrefix(ASCII.crlf) { - case .index(let index): - buffer.removeSubrange(buffer.startIndex...Index - // Check that what follows is a colon, otherwise this is a malformed header field line. - // Source: RFC 7230, section 3.2.4. - switch buffer[endHeaderNameIndex...].firstIndexAfterPrefix([ASCII.colon]) { - case .index(let index): startHeaderValueWithWhitespaceIndex = index - case .reachedEndOfSelf: - state = .parsingPart(buffer, .parsingHeaderFields(headerFields)) - return .needsMore - case .unexpectedPrefix: - state = .finished - return .emitError(.missingColonAfterHeaderName) - } - guard - let startHeaderValueIndex = buffer[startHeaderValueWithWhitespaceIndex...] - .firstIndex(where: { !ASCII.optionalWhitespace.contains($0) }) - else { - state = .parsingPart(buffer, .parsingHeaderFields(headerFields)) - return .needsMore - } - - // Find the CRLF first, then remove any trailing whitespace. - guard - let endHeaderValueWithWhitespaceRange = buffer[startHeaderValueIndex...] - .firstRange(of: ASCII.crlf) - else { - state = .parsingPart(buffer, .parsingHeaderFields(headerFields)) - return .needsMore - } - let headerFieldValueBytes = buffer[ - startHeaderValueIndex..?) -> ReceivedChunkAction { - switch state { - case .parsingInitialBoundary(var buffer): - guard let chunk else { return .emitError(.incompleteMultipartMessage) } - state = .mutating - buffer.append(contentsOf: chunk) - state = .parsingInitialBoundary(buffer) - return .none - case .parsingPart(var buffer, let part): - guard let chunk else { return .emitError(.incompleteMultipartMessage) } - state = .mutating - buffer.append(contentsOf: chunk) - state = .parsingPart(buffer, part) - return .none - case .finished: - guard chunk == nil else { return .emitError(.receivedChunkWhenFinished) } - return .returnNil - case .mutating: preconditionFailure("Invalid state: \(state)") - } - } - } -} diff --git a/Sources/OpenAPIRuntime/Multipart/MultipartFramesToBytesSequence.swift b/Sources/OpenAPIRuntime/Multipart/MultipartFramesToBytesSequence.swift deleted file mode 100644 index 2d607116..00000000 --- a/Sources/OpenAPIRuntime/Multipart/MultipartFramesToBytesSequence.swift +++ /dev/null @@ -1,321 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import HTTPTypes - -/// A sequence that serializes multipart frames into bytes. -struct MultipartFramesToBytesSequence: Sendable -where Upstream.Element == MultipartFrame { - - /// The source of multipart frames. - var upstream: Upstream - - /// The boundary string used to separate multipart parts. - var boundary: String -} - -extension MultipartFramesToBytesSequence: AsyncSequence { - - /// The type of element produced by this asynchronous sequence. - typealias Element = ArraySlice - - /// Creates the asynchronous iterator that produces elements of this - /// asynchronous sequence. - /// - /// - Returns: An instance of the `AsyncIterator` type used to produce - /// elements of the asynchronous sequence. - func makeAsyncIterator() -> Iterator { - Iterator(upstream: upstream.makeAsyncIterator(), boundary: boundary) - } - - /// An iterator that pulls frames from the upstream iterator and provides - /// serialized byte chunks. - struct Iterator: AsyncIteratorProtocol - where UpstreamIterator.Element == MultipartFrame { - - /// The iterator that provides the multipart frames. - private var upstream: UpstreamIterator - - /// The multipart frame serializer. - private var serializer: MultipartSerializer - - /// Creates a new iterator from the provided source of frames and a boundary string. - /// - Parameters: - /// - upstream: The iterator that provides the multipart frames. - /// - boundary: The boundary separating the multipart parts. - init(upstream: UpstreamIterator, boundary: String) { - self.upstream = upstream - self.serializer = .init(boundary: boundary) - } - - /// Asynchronously advances to the next element and returns it, or ends the - /// sequence if there is no next element. - /// - /// - Returns: The next element, if it exists, or `nil` to signal the end of - /// the sequence. - mutating func next() async throws -> ArraySlice? { - try await serializer.next { try await upstream.next() } - } - } -} - -/// A serializer of multipart frames into bytes. -struct MultipartSerializer { - - /// The boundary that separates parts. - private let boundary: ArraySlice - - /// The underlying state machine. - private var stateMachine: StateMachine - - /// The buffer of bytes ready to be written out. - private var outBuffer: [UInt8] - - /// Creates a new serializer. - /// - Parameter boundary: The boundary that separates parts. - init(boundary: String) { - self.boundary = ArraySlice(boundary.utf8) - self.stateMachine = .init() - self.outBuffer = [] - } - - /// Requests the next byte chunk. - /// - Parameter fetchFrame: A closure that is called when the serializer is ready to serialize the next frame. - /// - Returns: A byte chunk. - /// - Throws: When a serialization error is encountered. - mutating func next(_ fetchFrame: () async throws -> MultipartFrame?) async throws -> ArraySlice? { - - func flushedBytes() -> ArraySlice { - let outChunk = ArraySlice(outBuffer) - outBuffer.removeAll(keepingCapacity: true) - return outChunk - } - - while true { - switch stateMachine.next() { - case .returnNil: return nil - case .emitStart: - emitStart() - return flushedBytes() - case .needsMore: - let frame = try await fetchFrame() - switch stateMachine.receivedFrame(frame) { - case .returnNil: return nil - case .emitEvents(let events): - for event in events { - switch event { - case .headerFields(let headerFields): emitHeaders(headerFields) - case .bodyChunk(let chunk): emitBodyChunk(chunk) - case .endOfPart: emitEndOfPart() - case .start: emitStart() - case .end: emitEnd() - } - } - return flushedBytes() - case .emitError(let error): throw SerializerError(error: error) - } - } - } - } -} - -extension MultipartSerializer { - - /// An error thrown by the serializer. - struct SerializerError: Swift.Error, CustomStringConvertible, LocalizedError { - - /// The underlying error emitted by the state machine. - var error: StateMachine.ActionError - - var description: String { - switch error { - case .noHeaderFieldsAtStart: return "No header fields found at the start of the multipart body." - } - } - - var errorDescription: String? { description } - } -} - -extension MultipartSerializer { - - /// Writes the provided header fields into the buffer. - /// - Parameter headerFields: The header fields to serialize. - private mutating func emitHeaders(_ headerFields: HTTPFields) { - outBuffer.append(contentsOf: ASCII.crlf) - let sortedHeaders = headerFields.sorted { a, b in a.name.canonicalName < b.name.canonicalName } - for headerField in sortedHeaders { - outBuffer.append(contentsOf: headerField.name.canonicalName.utf8) - outBuffer.append(contentsOf: ASCII.colonSpace) - outBuffer.append(contentsOf: headerField.value.utf8) - outBuffer.append(contentsOf: ASCII.crlf) - } - outBuffer.append(contentsOf: ASCII.crlf) - } - - /// Writes the part body chunk into the buffer. - /// - Parameter bodyChunk: The body chunk to write. - private mutating func emitBodyChunk(_ bodyChunk: ArraySlice) { outBuffer.append(contentsOf: bodyChunk) } - - /// Writes an end of part boundary into the buffer. - private mutating func emitEndOfPart() { - outBuffer.append(contentsOf: ASCII.crlf) - outBuffer.append(contentsOf: ASCII.dashes) - outBuffer.append(contentsOf: boundary) - } - - /// Writes the start boundary into the buffer. - private mutating func emitStart() { - outBuffer.append(contentsOf: ASCII.dashes) - outBuffer.append(contentsOf: boundary) - } - - /// Writes the end double dash to the buffer. - private mutating func emitEnd() { - outBuffer.append(contentsOf: ASCII.dashes) - outBuffer.append(contentsOf: ASCII.crlf) - outBuffer.append(contentsOf: ASCII.crlf) - } -} - -extension MultipartSerializer { - - /// A state machine representing the multipart frame serializer. - struct StateMachine { - - /// The possible states of the state machine. - enum State: Hashable { - - /// Has not yet written any bytes. - case initial - - /// Emitted start, but no frames yet. - case startedNothingEmittedYet - - /// Finished, the terminal state. - case finished - - /// Last emitted a header fields frame. - case emittedHeaders - - /// Last emitted a part body chunk frame. - case emittedBodyChunk - } - - /// The current state of the state machine. - private(set) var state: State - - /// Creates a new state machine. - init() { self.state = .initial } - - /// An error returned by the state machine. - enum ActionError: Hashable { - - /// The first frame from upstream was not a header fields frame. - case noHeaderFieldsAtStart - } - - /// An action returned by the `next` method. - enum NextAction: Hashable { - - /// Return nil to the caller, no more bytes. - case returnNil - - /// Emit the initial boundary. - case emitStart - - /// Ready for the next frame. - case needsMore - } - - /// Read the next byte chunk serialized from upstream frames. - /// - Returns: An action to perform. - mutating func next() -> NextAction { - switch state { - case .initial: - state = .startedNothingEmittedYet - return .emitStart - case .finished: return .returnNil - case .startedNothingEmittedYet, .emittedHeaders, .emittedBodyChunk: return .needsMore - } - } - - /// An event to serialize to bytes. - enum Event: Hashable { - - /// The header fields of a part. - case headerFields(HTTPFields) - - /// A byte chunk of a part. - case bodyChunk(ArraySlice) - - /// A boundary between parts. - case endOfPart - - /// The initial boundary. - case start - - /// The final dashes. - case end - } - - /// An action returned by the `receivedFrame` method. - enum ReceivedFrameAction: Hashable { - - /// Return nil to the caller, no more bytes. - case returnNil - - /// Write the provided events as bytes. - case emitEvents([Event]) - - /// Throw the provided error. - case emitError(ActionError) - } - - /// Ingest the provided frame. - /// - Parameter frame: A new frame. If `nil`, then the source of frames is finished. - /// - Returns: An action to perform. - mutating func receivedFrame(_ frame: MultipartFrame?) -> ReceivedFrameAction { - switch state { - case .initial: preconditionFailure("Invalid state: \(state)") - case .finished: return .returnNil - case .startedNothingEmittedYet, .emittedHeaders, .emittedBodyChunk: break - } - switch (state, frame) { - case (.initial, _), (.finished, _): preconditionFailure("Already handled above.") - case (_, .none): - state = .finished - return .emitEvents([.endOfPart, .end]) - case (.startedNothingEmittedYet, .headerFields(let headerFields)): - state = .emittedHeaders - return .emitEvents([.headerFields(headerFields)]) - case (.startedNothingEmittedYet, .bodyChunk): - state = .finished - return .emitError(.noHeaderFieldsAtStart) - case (.emittedHeaders, .headerFields(let headerFields)), - (.emittedBodyChunk, .headerFields(let headerFields)): - state = .emittedHeaders - return .emitEvents([.endOfPart, .headerFields(headerFields)]) - case (.emittedHeaders, .bodyChunk(let bodyChunk)), (.emittedBodyChunk, .bodyChunk(let bodyChunk)): - state = .emittedBodyChunk - return .emitEvents([.bodyChunk(bodyChunk)]) - } - } - } -} diff --git a/Sources/OpenAPIRuntime/Multipart/MultipartFramesToRawPartsSequence.swift b/Sources/OpenAPIRuntime/Multipart/MultipartFramesToRawPartsSequence.swift deleted file mode 100644 index c3bfb7dc..00000000 --- a/Sources/OpenAPIRuntime/Multipart/MultipartFramesToRawPartsSequence.swift +++ /dev/null @@ -1,398 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import HTTPTypes - -/// A sequence that parses raw multipart parts from multipart frames. -struct MultipartFramesToRawPartsSequence: Sendable -where Upstream.Element == MultipartFrame { - - /// The source of multipart frames. - var upstream: Upstream -} - -extension MultipartFramesToRawPartsSequence: AsyncSequence { - - /// The type of element produced by this asynchronous sequence. - typealias Element = MultipartRawPart - - /// Creates the asynchronous iterator that produces elements of this - /// asynchronous sequence. - /// - /// - Returns: An instance of the `AsyncIterator` type used to produce - /// elements of the asynchronous sequence. - func makeAsyncIterator() -> Iterator { Iterator(makeUpstreamIterator: { upstream.makeAsyncIterator() }) } - - /// An iterator that pulls frames from the upstream iterator and provides - /// raw multipart parts. - struct Iterator: AsyncIteratorProtocol { - - /// The underlying shared iterator. - var shared: SharedIterator - - /// The closure invoked to fetch the next byte chunk of the part's body. - var bodyClosure: @Sendable () async throws -> ArraySlice? - - /// Creates a new iterator. - /// - Parameter makeUpstreamIterator: A closure that creates the upstream source of frames. - init(makeUpstreamIterator: @Sendable () -> Upstream.AsyncIterator) { - let shared = SharedIterator(makeUpstreamIterator: makeUpstreamIterator) - self.shared = shared - self.bodyClosure = { try await shared.nextFromBodySubsequence() } - } - - /// Asynchronously advances to the next element and returns it, or ends the - /// sequence if there is no next element. - /// - /// - Returns: The next element, if it exists, or `nil` to signal the end of - /// the sequence. - mutating func next() async throws -> Element? { - try await shared.nextFromPartSequence(bodyClosure: bodyClosure) - } - } -} - -extension HTTPBody { - - /// Creates a new body from the provided header fields and body closure. - /// - Parameters: - /// - headerFields: The header fields to inspect for a `content-length` header. - /// - bodyClosure: A closure invoked to fetch the next byte chunk of the body. - fileprivate convenience init( - headerFields: HTTPFields, - bodyClosure: @escaping @Sendable () async throws -> ArraySlice? - ) { - let stream = AsyncThrowingStream(unfolding: bodyClosure) - let length: HTTPBody.Length - if let contentLengthString = headerFields[.contentLength], let contentLength = Int(contentLengthString) { - length = .known(Int64(contentLength)) - } else { - length = .unknown - } - self.init(stream, length: length) - } -} - -extension MultipartFramesToRawPartsSequence { - - /// A state machine representing the frame to raw part parser. - struct StateMachine { - - /// The possible states of the state machine. - enum State: Hashable { - - /// Has not started parsing any parts yet. - case initial - - /// Waiting to send header fields to start a new part. - /// - /// Associated value is optional headers. - /// If they're non-nil, they arrived already, so just send them right away. - /// If they're nil, you need to fetch the next frame to get them. - case waitingToSendHeaders(HTTPFields?) - - /// In the process of streaming the byte chunks of a part body. - case streamingBody - - /// Finished, the terminal state. - case finished - } - - /// The current state of the state machine. - private(set) var state: State - - /// Creates a new state machine. - init() { self.state = .initial } - - /// An error returned by the state machine. - enum ActionError: Hashable { - - /// The outer, raw part sequence called next before the current part's body was fully consumed. - /// - /// This is a usage error by the consumer of the sequence. - case partSequenceNextCalledBeforeBodyWasConsumed - - /// The first frame received was a body chunk instead of header fields, which is invalid. - /// - /// This indicates an issue in the source of frames. - case receivedBodyChunkInInitial - - /// Received a body chunk when waiting for header fields, which is invalid. - /// - /// This indicates an issue in the source of frames. - case receivedBodyChunkWhenWaitingForHeaders - - /// Received another frame before having had a chance to send out header fields, this is an error caused - /// by the driver of the state machine. - case receivedFrameWhenAlreadyHasUnsentHeaders - } - - /// An action returned by the `nextFromPartSequence` method. - enum NextFromPartSequenceAction: Hashable { - - /// Return nil to the caller, no more parts. - case returnNil - - /// Fetch the next frame. - case fetchFrame - - /// Throw the provided error. - case emitError(ActionError) - - /// Emit a part with the provided header fields. - case emitPart(HTTPFields) - } - - /// Read the next part from the upstream frames. - /// - Returns: An action to perform. - mutating func nextFromPartSequence() -> NextFromPartSequenceAction { - switch state { - case .initial: - state = .waitingToSendHeaders(nil) - return .fetchFrame - case .waitingToSendHeaders(.some(let headers)): - state = .streamingBody - return .emitPart(headers) - case .waitingToSendHeaders(.none), .streamingBody: - state = .finished - return .emitError(.partSequenceNextCalledBeforeBodyWasConsumed) - case .finished: return .returnNil - } - } - - /// An action returned by the `partReceivedFrame` method. - enum PartReceivedFrameAction: Hashable { - - /// Return nil to the caller, no more parts. - case returnNil - - /// Throw the provided error. - case emitError(ActionError) - - /// Emit a part with the provided header fields. - case emitPart(HTTPFields) - } - - /// Ingest the provided frame, requested by the part sequence. - /// - Parameter frame: A new frame. If `nil`, then the source of frames is finished. - /// - Returns: An action to perform. - mutating func partReceivedFrame(_ frame: MultipartFrame?) -> PartReceivedFrameAction { - switch state { - case .initial: preconditionFailure("Haven't asked for a part chunk, how did we receive one?") - case .waitingToSendHeaders(.some): - state = .finished - return .emitError(.receivedFrameWhenAlreadyHasUnsentHeaders) - case .waitingToSendHeaders(.none): - if let frame { - switch frame { - case .headerFields(let headers): - state = .streamingBody - return .emitPart(headers) - case .bodyChunk: - state = .finished - return .emitError(.receivedBodyChunkWhenWaitingForHeaders) - } - } else { - state = .finished - return .returnNil - } - case .streamingBody: - state = .finished - return .emitError(.partSequenceNextCalledBeforeBodyWasConsumed) - case .finished: return .returnNil - } - } - - /// An action returned by the `nextFromBodySubsequence` method. - enum NextFromBodySubsequenceAction: Hashable { - - /// Return nil to the caller, no more byte chunks. - case returnNil - - /// Fetch the next frame. - case fetchFrame - - /// Throw the provided error. - case emitError(ActionError) - } - - /// Read the next byte chunk requested by the current part's body sequence. - /// - Returns: An action to perform. - mutating func nextFromBodySubsequence() -> NextFromBodySubsequenceAction { - switch state { - case .initial: - state = .finished - return .emitError(.receivedBodyChunkInInitial) - case .waitingToSendHeaders: - state = .finished - return .emitError(.receivedBodyChunkWhenWaitingForHeaders) - case .streamingBody: return .fetchFrame - case .finished: return .returnNil - } - } - - /// An action returned by the `bodyReceivedFrame` method. - enum BodyReceivedFrameAction: Hashable { - - /// Return nil to the caller, no more byte chunks. - case returnNil - - /// Return the provided byte chunk. - case returnChunk(ArraySlice) - - /// Throw the provided error. - case emitError(ActionError) - } - - /// Ingest the provided frame, requested by the body sequence. - /// - Parameter frame: A new frame. If `nil`, then the source of frames is finished. - /// - Returns: An action to perform. - mutating func bodyReceivedFrame(_ frame: MultipartFrame?) -> BodyReceivedFrameAction { - switch state { - case .initial: preconditionFailure("Haven't asked for a frame, how did we receive one?") - case .waitingToSendHeaders: - state = .finished - return .emitError(.receivedBodyChunkWhenWaitingForHeaders) - case .streamingBody: - if let frame { - switch frame { - case .headerFields(let headers): - state = .waitingToSendHeaders(headers) - return .returnNil - case .bodyChunk(let bodyChunk): return .returnChunk(bodyChunk) - } - } else { - state = .finished - return .returnNil - } - case .finished: return .returnNil - } - } - } -} - -extension MultipartFramesToRawPartsSequence { - - /// A type-safe iterator shared by the outer part sequence iterator and an inner body sequence iterator. - /// - /// It enforces that when a new part is emitted by the outer sequence, that the new part's body is then fully - /// consumed before the outer sequence is asked for the next part. - /// - /// This is required as the source of bytes is a single stream, so without the current part's body being consumed, - /// we can't move on to the next part. - actor SharedIterator { - - /// The upstream source of frames. - private var upstream: Upstream.AsyncIterator - - /// The underlying state machine. - private var stateMachine: StateMachine - - /// Creates a new iterator. - /// - Parameter makeUpstreamIterator: A closure that creates the upstream source of frames. - init(makeUpstreamIterator: @Sendable () -> Upstream.AsyncIterator) { - let upstream = makeUpstreamIterator() - self.upstream = upstream - self.stateMachine = .init() - } - - /// An error thrown by the shared iterator. - struct IteratorError: Swift.Error, CustomStringConvertible, LocalizedError { - - /// The underlying error emitted by the state machine. - let error: StateMachine.ActionError - - var description: String { - switch error { - case .partSequenceNextCalledBeforeBodyWasConsumed: - return - "The outer part sequence was asked for the next element before the current part's inner body sequence was fully consumed." - case .receivedBodyChunkInInitial: - return - "Received a body chunk from the upstream sequence as the first element, instead of header fields." - case .receivedBodyChunkWhenWaitingForHeaders: - return "Received a body chunk from the upstream sequence when expecting header fields." - case .receivedFrameWhenAlreadyHasUnsentHeaders: - return "Received another frame before the current frame with header fields was written out." - } - } - - var errorDescription: String? { description } - } - - /// Request the next element from the outer part sequence. - /// - Parameter bodyClosure: The closure invoked to fetch the next byte chunk of the part's body. - /// - Returns: The next element, or `nil` if finished. - /// - Throws: When a parsing error is encountered. - func nextFromPartSequence(bodyClosure: @escaping @Sendable () async throws -> ArraySlice?) async throws - -> Element? - { - switch stateMachine.nextFromPartSequence() { - case .returnNil: return nil - case .fetchFrame: - let frame: Upstream.AsyncIterator.Element? - var upstream = upstream - if #available(macOS 15, iOS 18.0, tvOS 18.0, watchOS 11.0, macCatalyst 18.0, visionOS 2.0, *) { - frame = try await upstream.next(isolation: self) - } else { - nonisolated(unsafe) var unsafeUpstream = upstream - frame = try await unsafeUpstream.next() - upstream = unsafeUpstream - } - self.upstream = upstream - switch stateMachine.partReceivedFrame(frame) { - case .returnNil: return nil - case .emitError(let error): throw IteratorError(error: error) - case .emitPart(let headers): - let body = HTTPBody(headerFields: headers, bodyClosure: bodyClosure) - return .init(headerFields: headers, body: body) - } - case .emitError(let error): throw IteratorError(error: error) - case .emitPart(let headers): - let body = HTTPBody(headerFields: headers, bodyClosure: bodyClosure) - return .init(headerFields: headers, body: body) - } - } - - /// Request the next element from the inner body bytes sequence. - /// - Returns: The next element, or `nil` if finished. - func nextFromBodySubsequence() async throws -> ArraySlice? { - switch stateMachine.nextFromBodySubsequence() { - case .returnNil: return nil - case .fetchFrame: - let frame: Upstream.AsyncIterator.Element? - var upstream = upstream - if #available(macOS 15, iOS 18.0, tvOS 18.0, watchOS 11.0, macCatalyst 18.0, visionOS 2.0, *) { - frame = try await upstream.next(isolation: self) - } else { - nonisolated(unsafe) var unsafeUpstream = upstream - frame = try await unsafeUpstream.next() - upstream = unsafeUpstream - } - self.upstream = upstream - switch stateMachine.bodyReceivedFrame(frame) { - case .returnNil: return nil - case .returnChunk(let bodyChunk): return bodyChunk - case .emitError(let error): throw IteratorError(error: error) - } - case .emitError(let error): throw IteratorError(error: error) - } - } - } -} diff --git a/Sources/OpenAPIRuntime/Multipart/MultipartInternalTypes.swift b/Sources/OpenAPIRuntime/Multipart/MultipartInternalTypes.swift deleted file mode 100644 index 49e57b9f..00000000 --- a/Sources/OpenAPIRuntime/Multipart/MultipartInternalTypes.swift +++ /dev/null @@ -1,26 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import HTTPTypes - -/// A frame of a multipart message, either the whole header fields -/// section or a chunk of the body bytes. -enum MultipartFrame: Sendable, Hashable { - - /// The header fields section. - case headerFields(HTTPFields) - - /// One byte chunk of the part's body. - case bodyChunk(ArraySlice) -} diff --git a/Sources/OpenAPIRuntime/Multipart/MultipartRawPartsToFramesSequence.swift b/Sources/OpenAPIRuntime/Multipart/MultipartRawPartsToFramesSequence.swift deleted file mode 100644 index 71b9c82a..00000000 --- a/Sources/OpenAPIRuntime/Multipart/MultipartRawPartsToFramesSequence.swift +++ /dev/null @@ -1,223 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import HTTPTypes - -/// A sequence that serializes raw multipart parts into multipart frames. -struct MultipartRawPartsToFramesSequence: Sendable -where Upstream.Element == MultipartRawPart { - - /// The source of raw parts. - var upstream: Upstream -} - -extension MultipartRawPartsToFramesSequence: AsyncSequence { - - /// The type of element produced by this asynchronous sequence. - typealias Element = MultipartFrame - - /// Creates the asynchronous iterator that produces elements of this - /// asynchronous sequence. - /// - /// - Returns: An instance of the `AsyncIterator` type used to produce - /// elements of the asynchronous sequence. - func makeAsyncIterator() -> Iterator { Iterator(upstream: upstream.makeAsyncIterator()) } - - /// An iterator that pulls raw parts from the upstream iterator and provides - /// multipart frames. - struct Iterator: AsyncIteratorProtocol { - - /// The iterator that provides the raw parts. - var upstream: Upstream.AsyncIterator - - /// The underlying parts to frames serializer. - var serializer: Serializer - - /// Creates a new iterator. - /// - Parameter upstream: The iterator that provides the raw parts. - init(upstream: Upstream.AsyncIterator) { - self.upstream = upstream - self.serializer = .init(upstream: upstream) - } - - /// Asynchronously advances to the next element and returns it, or ends the - /// sequence if there is no next element. - /// - /// - Returns: The next element, if it exists, or `nil` to signal the end of - /// the sequence. - mutating func next() async throws -> Element? { try await serializer.next() } - } -} - -extension MultipartRawPartsToFramesSequence { - - /// A state machine representing the raw part to frame serializer. - struct StateMachine { - - /// The possible states of the state machine. - enum State { - - /// Has not emitted any frames yet. - case initial - - /// Waiting for the next part. - case waitingForPart - - /// Returning body chunks from the current part's body. - case streamingBody(HTTPBody.AsyncIterator) - - /// Finished, the terminal state. - case finished - } - - /// The current state of the state machine. - private(set) var state: State - - /// Creates a new state machine. - init() { self.state = .initial } - - /// An action returned by the `next` method. - enum NextAction { - - /// Return nil to the caller, no more parts. - case returnNil - - /// Fetch the next part. - case fetchPart - - /// Fetch the next body chunk from the provided iterator. - case fetchBodyChunk(HTTPBody.AsyncIterator) - } - - /// Read the next part from the upstream frames. - /// - Returns: An action to perform. - mutating func next() -> NextAction { - switch state { - case .initial: - state = .waitingForPart - return .fetchPart - case .streamingBody(let iterator): return .fetchBodyChunk(iterator) - case .finished: return .returnNil - case .waitingForPart: preconditionFailure("Invalid state: \(state)") - } - } - - /// An action returned by the `receivedPart` method. - enum ReceivedPartAction: Hashable { - - /// Return nil to the caller, no more frames. - case returnNil - - /// Return the provided header fields. - case emitHeaderFields(HTTPFields) - } - - /// Ingest the provided part. - /// - Parameter part: A new part. If `nil`, then the source of parts is finished. - /// - Returns: An action to perform. - mutating func receivedPart(_ part: MultipartRawPart?) -> ReceivedPartAction { - switch state { - case .waitingForPart: - if let part { - state = .streamingBody(part.body.makeAsyncIterator()) - return .emitHeaderFields(part.headerFields) - } else { - state = .finished - return .returnNil - } - case .finished: return .returnNil - case .initial, .streamingBody: preconditionFailure("Invalid state: \(state)") - } - } - - /// An action returned by the `receivedBodyChunk` method. - enum ReceivedBodyChunkAction: Hashable { - - /// Return nil to the caller, no more frames. - case returnNil - - /// Fetch the next part. - case fetchPart - - /// Return the provided body chunk. - case emitBodyChunk(ArraySlice) - } - - /// Ingest the provided part. - /// - Parameter bodyChunk: A new body chunk. If `nil`, then the current part's body is finished. - /// - Returns: An action to perform. - mutating func receivedBodyChunk(_ bodyChunk: ArraySlice?) -> ReceivedBodyChunkAction { - switch state { - case .streamingBody: - if let bodyChunk { - return .emitBodyChunk(bodyChunk) - } else { - state = .waitingForPart - return .fetchPart - } - case .finished: return .returnNil - case .initial, .waitingForPart: preconditionFailure("Invalid state: \(state)") - } - } - } -} - -extension MultipartRawPartsToFramesSequence { - - /// A serializer of multipart raw parts into multipart frames. - struct Serializer { - - /// The upstream source of raw parts. - private var upstream: Upstream.AsyncIterator - - /// The underlying state machine. - private var stateMachine: StateMachine - - /// Creates a new iterator. - /// - Parameter upstream: The upstream source of raw parts. - init(upstream: Upstream.AsyncIterator) { - self.upstream = upstream - self.stateMachine = .init() - } - - /// Requests the next frame. - /// - Returns: A frame. - /// - Throws: When a serialization error is encountered. - mutating func next() async throws -> MultipartFrame? { - func handleFetchPart() async throws -> MultipartFrame? { - let part = try await upstream.next() - switch stateMachine.receivedPart(part) { - case .returnNil: return nil - case .emitHeaderFields(let headerFields): return .headerFields(headerFields) - } - } - switch stateMachine.next() { - case .returnNil: return nil - case .fetchPart: return try await handleFetchPart() - case .fetchBodyChunk(var iterator): - let bodyChunk = try await iterator.next() - switch stateMachine.receivedBodyChunk(bodyChunk) { - case .returnNil: return nil - case .fetchPart: return try await handleFetchPart() - case .emitBodyChunk(let bodyChunk): return .bodyChunk(bodyChunk) - } - } - } - } -} diff --git a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartBytesToFramesSequence.swift b/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartBytesToFramesSequence.swift deleted file mode 100644 index afc3d44e..00000000 --- a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartBytesToFramesSequence.swift +++ /dev/null @@ -1,186 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import XCTest -@_spi(Generated) @testable import OpenAPIRuntime -import HTTPTypes - -final class Test_MultipartBytesToFramesSequence: Test_Runtime { - func test() async throws { - let chunk = chunkFromStringLines([ - "--__abcd__", #"Content-Disposition: form-data; name="name""#, "", "24", "--__abcd__", - #"Content-Disposition: form-data; name="info""#, "", "{}", "--__abcd__--", - ]) - let upstream = chunk.async.map { ArraySlice([$0]) } - let sequence = MultipartBytesToFramesSequence(upstream: upstream, boundary: "__abcd__") - var frames: [MultipartFrame] = [] - for try await frame in sequence { frames.append(frame) } - XCTAssertEqual( - frames, - [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("2")), - .bodyChunk(chunkFromString("4")), .headerFields([.contentDisposition: #"form-data; name="info""#]), - .bodyChunk(chunkFromString("{")), .bodyChunk(chunkFromString("}")), - ] - ) - } -} - -final class Test_MultipartParser: Test_Runtime { - func test() async throws { - var chunk = chunkFromStringLines([ - "--__abcd__", #"Content-Disposition: form-data; name="name""#, "", "24", "--__abcd__", - #"Content-Disposition: form-data; name="info""#, "", "{}", "--__abcd__--", - ]) - var parser = MultipartParser(boundary: "__abcd__") - let next: () async throws -> ArraySlice? = { - if let first = chunk.first { - let out: ArraySlice = [first] - chunk = chunk.dropFirst() - return out - } else { - return nil - } - } - var frames: [MultipartFrame] = [] - while let frame = try await parser.next(next) { frames.append(frame) } - XCTAssertEqual( - frames, - [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("2")), - .bodyChunk(chunkFromString("4")), .headerFields([.contentDisposition: #"form-data; name="info""#]), - .bodyChunk(chunkFromString("{")), .bodyChunk(chunkFromString("}")), - ] - ) - } -} - -private func newStateMachine() -> MultipartParser.StateMachine { .init(boundary: "__abcd__") } - -final class Test_MultipartParserStateMachine: Test_Runtime { - - func testInvalidInitialBoundary() throws { - var stateMachine = newStateMachine() - XCTAssertEqual(stateMachine.receivedChunk(chunkFromString("invalid")), .none) - XCTAssertEqual(stateMachine.readNextPart(), .emitError(.invalidInitialBoundary)) - } - - func testHeaderFields() throws { - var stateMachine = newStateMachine() - XCTAssertEqual(stateMachine.receivedChunk(chunkFromString("--__ab")), .none) - XCTAssertEqual(stateMachine.readNextPart(), .needsMore) - XCTAssertEqual(stateMachine.state, .parsingInitialBoundary(bufferFromString("--__ab"))) - XCTAssertEqual(stateMachine.receivedChunk(chunkFromString("cd__", addCRLFs: 1)), .none) - XCTAssertEqual(stateMachine.readNextPart(), .none) - XCTAssertEqual(stateMachine.state, .parsingPart([0x0d, 0x0a], .parsingHeaderFields(.init()))) - XCTAssertEqual(stateMachine.receivedChunk(chunkFromString(#"Content-Disposi"#)), .none) - XCTAssertEqual( - stateMachine.state, - .parsingPart([0x0d, 0x0a] + bufferFromString(#"Content-Disposi"#), .parsingHeaderFields(.init())) - ) - XCTAssertEqual(stateMachine.readNextPart(), .needsMore) - XCTAssertEqual( - stateMachine.receivedChunk(chunkFromString(#"tion: form-data; name="name""#, addCRLFs: 2)), - .none - ) - XCTAssertEqual( - stateMachine.state, - .parsingPart( - [0x0d, 0x0a] + bufferFromString(#"Content-Disposition: form-data; name="name""#) + [ - 0x0d, 0x0a, 0x0d, 0x0a, - ], - .parsingHeaderFields(.init()) - ) - ) - // Reads the first header field. - XCTAssertEqual(stateMachine.readNextPart(), .none) - // Reads the end of the header fields section. - XCTAssertEqual( - stateMachine.readNextPart(), - .emitHeaderFields([.contentDisposition: #"form-data; name="name""#]) - ) - XCTAssertEqual(stateMachine.state, .parsingPart([], .parsingBody)) - } - - func testPartBody() throws { - var stateMachine = newStateMachine() - let chunk = chunkFromStringLines(["--__abcd__", #"Content-Disposition: form-data; name="name""#, "", "24"]) - XCTAssertEqual(stateMachine.receivedChunk(chunk), .none) - XCTAssertEqual(stateMachine.state, .parsingInitialBoundary(Array(chunk))) - // Parse the initial boundary and first header field. - for _ in 0..<2 { XCTAssertEqual(stateMachine.readNextPart(), .none) } - // Parse the end of header fields. - XCTAssertEqual( - stateMachine.readNextPart(), - .emitHeaderFields([.contentDisposition: #"form-data; name="name""#]) - ) - XCTAssertEqual(stateMachine.state, .parsingPart(bufferFromString(#"24"#) + [0x0d, 0x0a], .parsingBody)) - XCTAssertEqual(stateMachine.receivedChunk(chunkFromString(".42")), .none) - XCTAssertEqual( - stateMachine.state, - .parsingPart(bufferFromString("24") + [0x0d, 0x0a] + bufferFromString(".42"), .parsingBody) - ) - XCTAssertEqual( - stateMachine.readNextPart(), - .emitBodyChunk(bufferFromString("24") + [0x0d, 0x0a] + bufferFromString(".42")) - ) - XCTAssertEqual(stateMachine.state, .parsingPart([], .parsingBody)) - XCTAssertEqual(stateMachine.receivedChunk([0x0d, 0x0a] + chunkFromString("--__ab")), .none) - XCTAssertEqual(stateMachine.state, .parsingPart([0x0d, 0x0a] + chunkFromString("--__ab"), .parsingBody)) - XCTAssertEqual(stateMachine.readNextPart(), .needsMore) - XCTAssertEqual(stateMachine.receivedChunk(chunkFromString("cd__--", addCRLFs: 1)), .none) - XCTAssertEqual( - stateMachine.state, - .parsingPart([0x0d, 0x0a] + chunkFromString("--__abcd__--", addCRLFs: 1), .parsingBody) - ) - // Parse the final boundary. - XCTAssertEqual(stateMachine.readNextPart(), .none) - // Parse the trailing two dashes. - XCTAssertEqual(stateMachine.readNextPart(), .returnNil) - } - - func testTwoParts() throws { - var stateMachine = newStateMachine() - let chunk = chunkFromStringLines([ - "--__abcd__", #"Content-Disposition: form-data; name="name""#, "", "24", "--__abcd__", - #"Content-Disposition: form-data; name="info""#, "", "{}", "--__abcd__--", - ]) - XCTAssertEqual(stateMachine.receivedChunk(chunk), .none) - // Parse the initial boundary and first header field. - for _ in 0..<2 { XCTAssertEqual(stateMachine.readNextPart(), .none) } - // Parse the end of header fields. - XCTAssertEqual( - stateMachine.readNextPart(), - .emitHeaderFields([.contentDisposition: #"form-data; name="name""#]) - ) - // Parse the first part's body. - XCTAssertEqual(stateMachine.readNextPart(), .emitBodyChunk(chunkFromString("24"))) - // Parse the boundary. - XCTAssertEqual(stateMachine.readNextPart(), .none) - // Parse the end of header fields. - XCTAssertEqual( - stateMachine.readNextPart(), - .emitHeaderFields([.contentDisposition: #"form-data; name="info""#]) - ) - // Parse the second part's body. - XCTAssertEqual(stateMachine.readNextPart(), .emitBodyChunk(chunkFromString("{}"))) - // Parse the trailing two dashes. - XCTAssertEqual(stateMachine.readNextPart(), .returnNil) - } -} diff --git a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToBytesSequence.swift b/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToBytesSequence.swift deleted file mode 100644 index ba05083e..00000000 --- a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToBytesSequence.swift +++ /dev/null @@ -1,104 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import XCTest -@_spi(Generated) @testable import OpenAPIRuntime -import HTTPTypes - -final class Test_MultipartFramesToBytesSequence: Test_Runtime { - func test() async throws { - let frames: [MultipartFrame] = [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("2")), - .bodyChunk(chunkFromString("4")), .headerFields([.contentDisposition: #"form-data; name="info""#]), - .bodyChunk(chunkFromString("{")), .bodyChunk(chunkFromString("}")), - ] - let upstream = frames.async - let sequence = MultipartFramesToBytesSequence(upstream: upstream, boundary: "__abcd__") - var bytes: ArraySlice = [] - for try await chunk in sequence { bytes.append(contentsOf: chunk) } - let expectedBytes = chunkFromStringLines([ - "--__abcd__", #"content-disposition: form-data; name="name""#, "", "24", "--__abcd__", - #"content-disposition: form-data; name="info""#, "", "{}", "--__abcd__--", "", - ]) - XCTAssertEqualData(bytes, expectedBytes) - } -} - -final class Test_MultipartSerializer: Test_Runtime { - func test() async throws { - let frames: [MultipartFrame] = [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("2")), - .bodyChunk(chunkFromString("4")), .headerFields([.contentDisposition: #"form-data; name="info""#]), - .bodyChunk(chunkFromString("{")), .bodyChunk(chunkFromString("}")), - ] - var serializer = MultipartSerializer(boundary: "__abcd__") - var iterator = frames.makeIterator() - var bytes: [UInt8] = [] - while let chunk = try await serializer.next({ iterator.next() }) { bytes.append(contentsOf: chunk) } - let expectedBytes = chunkFromStringLines([ - "--__abcd__", #"content-disposition: form-data; name="name""#, "", "24", "--__abcd__", - #"content-disposition: form-data; name="info""#, "", "{}", "--__abcd__--", "", - ]) - XCTAssertEqualData(bytes, expectedBytes) - } -} - -private func newStateMachine() -> MultipartSerializer.StateMachine { .init() } - -final class Test_MultipartSerializerStateMachine: Test_Runtime { - - func testInvalidFirstFrame() throws { - var stateMachine = newStateMachine() - XCTAssertEqual(stateMachine.next(), .emitStart) - XCTAssertEqual(stateMachine.next(), .needsMore) - XCTAssertEqual(stateMachine.receivedFrame(.bodyChunk([])), .emitError(.noHeaderFieldsAtStart)) - } - - func testTwoParts() throws { - var stateMachine = newStateMachine() - XCTAssertEqual(stateMachine.state, .initial) - XCTAssertEqual(stateMachine.next(), .emitStart) - XCTAssertEqual(stateMachine.state, .startedNothingEmittedYet) - XCTAssertEqual( - stateMachine.receivedFrame(.headerFields([.contentDisposition: #"form-data; name="name""#])), - .emitEvents([.headerFields([.contentDisposition: #"form-data; name="name""#])]) - ) - XCTAssertEqual(stateMachine.state, .emittedHeaders) - XCTAssertEqual(stateMachine.next(), .needsMore) - XCTAssertEqual( - stateMachine.receivedFrame(.bodyChunk(chunkFromString("24"))), - .emitEvents([.bodyChunk(chunkFromString("24"))]) - ) - XCTAssertEqual(stateMachine.state, .emittedBodyChunk) - XCTAssertEqual(stateMachine.next(), .needsMore) - XCTAssertEqual( - stateMachine.receivedFrame(.headerFields([.contentDisposition: #"form-data; name="info""#])), - .emitEvents([.endOfPart, .headerFields([.contentDisposition: #"form-data; name="info""#])]) - ) - XCTAssertEqual(stateMachine.state, .emittedHeaders) - XCTAssertEqual(stateMachine.next(), .needsMore) - XCTAssertEqual( - stateMachine.receivedFrame(.bodyChunk(chunkFromString("{}"))), - .emitEvents([.bodyChunk(chunkFromString("{}"))]) - ) - XCTAssertEqual(stateMachine.state, .emittedBodyChunk) - XCTAssertEqual(stateMachine.next(), .needsMore) - XCTAssertEqual(stateMachine.receivedFrame(nil), .emitEvents([.endOfPart, .end])) - } -} diff --git a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToRawPartsSequence.swift b/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToRawPartsSequence.swift deleted file mode 100644 index 933803fb..00000000 --- a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartFramesToRawPartsSequence.swift +++ /dev/null @@ -1,138 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import XCTest -@_spi(Generated) @testable import OpenAPIRuntime -import HTTPTypes - -final class Test_MultipartFramesToRawPartsSequence: Test_Runtime { - func test() async throws { - let frames: [MultipartFrame] = [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("2")), - .bodyChunk(chunkFromString("4")), .headerFields([.contentDisposition: #"form-data; name="info""#]), - .bodyChunk(chunkFromString("{")), .bodyChunk(chunkFromString("}")), - ] - let upstream = frames.async - let sequence = MultipartFramesToRawPartsSequence(upstream: upstream) - var iterator = sequence.makeAsyncIterator() - guard let part1 = try await iterator.next() else { - XCTFail("Missing part") - return - } - XCTAssertEqual(part1.headerFields, [.contentDisposition: #"form-data; name="name""#]) - try await XCTAssertEqualStringifiedData(part1.body, "24") - guard let part2 = try await iterator.next() else { - XCTFail("Missing part") - return - } - XCTAssertEqual(part2.headerFields, [.contentDisposition: #"form-data; name="info""#]) - try await XCTAssertEqualStringifiedData(part2.body, "{}") - - let part3 = try await iterator.next() - XCTAssertNil(part3) - } -} - -final class Test_MultipartFramesToRawPartsSequenceIterator: Test_Runtime { - func test() async throws { - let frames: [MultipartFrame] = [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("2")), - .bodyChunk(chunkFromString("4")), .headerFields([.contentDisposition: #"form-data; name="info""#]), - .bodyChunk(chunkFromString("{")), .bodyChunk(chunkFromString("}")), - ] - let upstream = frames.async - let sharedIterator = MultipartFramesToRawPartsSequence> - .SharedIterator(makeUpstreamIterator: { upstream.makeAsyncIterator() }) - let bodyClosure: @Sendable () async throws -> ArraySlice? = { - try await sharedIterator.nextFromBodySubsequence() - } - guard let part1 = try await sharedIterator.nextFromPartSequence(bodyClosure: bodyClosure) else { - XCTFail("Missing part") - return - } - XCTAssertEqual(part1.headerFields, [.contentDisposition: #"form-data; name="name""#]) - try await XCTAssertEqualStringifiedData(part1.body, "24") - guard let part2 = try await sharedIterator.nextFromPartSequence(bodyClosure: bodyClosure) else { - XCTFail("Missing part") - return - } - XCTAssertEqual(part2.headerFields, [.contentDisposition: #"form-data; name="info""#]) - try await XCTAssertEqualStringifiedData(part2.body, "{}") - - let part3 = try await sharedIterator.nextFromPartSequence(bodyClosure: bodyClosure) - XCTAssertNil(part3) - } -} - -private func newStateMachine() -> MultipartFramesToRawPartsSequence>.StateMachine { - .init() -} - -final class Test_MultipartFramesToRawPartsSequenceIteratorStateMachine: Test_Runtime { - - func testInvalidFirstFrame() throws { - var stateMachine = newStateMachine() - XCTAssertEqual(stateMachine.state, .initial) - XCTAssertEqual(stateMachine.nextFromPartSequence(), .fetchFrame) - XCTAssertEqual(stateMachine.state, .waitingToSendHeaders(nil)) - XCTAssertEqual( - stateMachine.partReceivedFrame(.bodyChunk([])), - .emitError(.receivedBodyChunkWhenWaitingForHeaders) - ) - } - - func testTwoParts() throws { - var stateMachine = newStateMachine() - XCTAssertEqual(stateMachine.state, .initial) - XCTAssertEqual(stateMachine.nextFromPartSequence(), .fetchFrame) - XCTAssertEqual(stateMachine.state, .waitingToSendHeaders(nil)) - XCTAssertEqual( - stateMachine.partReceivedFrame(.headerFields([.contentDisposition: #"form-data; name="name""#])), - .emitPart([.contentDisposition: #"form-data; name="name""#]) - ) - XCTAssertEqual(stateMachine.state, .streamingBody) - XCTAssertEqual(stateMachine.nextFromBodySubsequence(), .fetchFrame) - XCTAssertEqual(stateMachine.state, .streamingBody) - XCTAssertEqual( - stateMachine.bodyReceivedFrame(.bodyChunk(chunkFromString("24"))), - .returnChunk(chunkFromString("24")) - ) - XCTAssertEqual(stateMachine.state, .streamingBody) - XCTAssertEqual(stateMachine.nextFromBodySubsequence(), .fetchFrame) - XCTAssertEqual( - stateMachine.bodyReceivedFrame(.headerFields([.contentDisposition: #"form-data; name="info""#])), - .returnNil - ) - XCTAssertEqual(stateMachine.state, .waitingToSendHeaders([.contentDisposition: #"form-data; name="info""#])) - XCTAssertEqual( - stateMachine.nextFromPartSequence(), - .emitPart([.contentDisposition: #"form-data; name="info""#]) - ) - XCTAssertEqual(stateMachine.state, .streamingBody) - XCTAssertEqual(stateMachine.nextFromBodySubsequence(), .fetchFrame) - XCTAssertEqual( - stateMachine.bodyReceivedFrame(.bodyChunk(chunkFromString("{}"))), - .returnChunk(chunkFromString("{}")) - ) - XCTAssertEqual(stateMachine.nextFromBodySubsequence(), .fetchFrame) - XCTAssertEqual(stateMachine.bodyReceivedFrame(nil), .returnNil) - XCTAssertEqual(stateMachine.state, .finished) - XCTAssertEqual(stateMachine.nextFromPartSequence(), .returnNil) - } -} diff --git a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartRawPartsToFramesSequence.swift b/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartRawPartsToFramesSequence.swift deleted file mode 100644 index ac312e29..00000000 --- a/Tests/OpenAPIRuntimeTests/Multipart/Test_MultipartRawPartsToFramesSequence.swift +++ /dev/null @@ -1,150 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif -import XCTest -@_spi(Generated) @testable import OpenAPIRuntime -import HTTPTypes - -final class Test_MultipartRawPartsToFramesSequence: Test_Runtime { - func test() async throws { - let secondPartChunks = "{}".utf8.async - let secondPartBody = HTTPBody( - secondPartChunks.map { ArraySlice([$0]) }, - length: .unknown, - iterationBehavior: .multiple - ) - let parts: [MultipartRawPart] = [ - .init(headerFields: [.contentDisposition: #"form-data; name="name""#], body: "24"), - .init(headerFields: [.contentDisposition: #"form-data; name="info""#], body: secondPartBody), - ] - let upstream = parts.async - let sequence = MultipartRawPartsToFramesSequence(upstream: upstream) - - var frames: [MultipartFrame] = [] - for try await frame in sequence { frames.append(frame) } - let expectedFrames: [MultipartFrame] = [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("24")), - .headerFields([.contentDisposition: #"form-data; name="info""#]), .bodyChunk(chunkFromString("{")), - .bodyChunk(chunkFromString("}")), - ] - XCTAssertEqual(frames, expectedFrames) - } -} - -final class Test_MultipartRawPartsToFramesSequenceSerializer: Test_Runtime { - func test() async throws { - let secondPartChunks = "{}".utf8.async - let secondPartBody = HTTPBody( - secondPartChunks.map { ArraySlice([$0]) }, - length: .unknown, - iterationBehavior: .multiple - ) - let parts: [MultipartRawPart] = [ - .init(headerFields: [.contentDisposition: #"form-data; name="name""#], body: "24"), - .init(headerFields: [.contentDisposition: #"form-data; name="info""#], body: secondPartBody), - ] - let upstream = parts.async - var serializer = MultipartRawPartsToFramesSequence> - .Serializer(upstream: upstream.makeAsyncIterator()) - var frames: [MultipartFrame] = [] - while let frame = try await serializer.next() { frames.append(frame) } - let expectedFrames: [MultipartFrame] = [ - .headerFields([.contentDisposition: #"form-data; name="name""#]), .bodyChunk(chunkFromString("24")), - .headerFields([.contentDisposition: #"form-data; name="info""#]), .bodyChunk(chunkFromString("{")), - .bodyChunk(chunkFromString("}")), - ] - XCTAssertEqual(frames, expectedFrames) - } -} - -private func newStateMachine() -> MultipartRawPartsToFramesSequence>.StateMachine { - .init() -} - -final class Test_MultipartRawPartsToFramesSequenceStateMachine: Test_Runtime { - - func testTwoParts() throws { - var stateMachine = newStateMachine() - XCTAssertTrue(stateMachine.state.isInitial) - XCTAssertTrue(stateMachine.next().isFetchPart) - XCTAssertTrue(stateMachine.state.isWaitingForPart) - XCTAssertEqual( - stateMachine.receivedPart( - .init(headerFields: [.contentDisposition: #"form-data; name="name""#], body: "24") - ), - .emitHeaderFields([.contentDisposition: #"form-data; name="name""#]) - ) - XCTAssertTrue(stateMachine.state.isStreamingBody) - XCTAssertTrue(stateMachine.next().isFetchBodyChunk) - XCTAssertEqual(stateMachine.receivedBodyChunk(chunkFromString("24")), .emitBodyChunk(chunkFromString("24"))) - XCTAssertTrue(stateMachine.state.isStreamingBody) - XCTAssertTrue(stateMachine.next().isFetchBodyChunk) - XCTAssertEqual(stateMachine.receivedBodyChunk(nil), .fetchPart) - XCTAssertEqual( - stateMachine.receivedPart( - .init(headerFields: [.contentDisposition: #"form-data; name="info""#], body: "{}") - ), - .emitHeaderFields([.contentDisposition: #"form-data; name="info""#]) - ) - XCTAssertTrue(stateMachine.state.isStreamingBody) - XCTAssertTrue(stateMachine.next().isFetchBodyChunk) - XCTAssertEqual(stateMachine.receivedBodyChunk(chunkFromString("{")), .emitBodyChunk(chunkFromString("{"))) - XCTAssertTrue(stateMachine.state.isStreamingBody) - XCTAssertTrue(stateMachine.next().isFetchBodyChunk) - XCTAssertEqual(stateMachine.receivedBodyChunk(chunkFromString("}")), .emitBodyChunk(chunkFromString("}"))) - XCTAssertTrue(stateMachine.state.isStreamingBody) - XCTAssertTrue(stateMachine.next().isFetchBodyChunk) - XCTAssertEqual(stateMachine.receivedBodyChunk(nil), .fetchPart) - XCTAssertEqual(stateMachine.receivedPart(nil), .returnNil) - } -} - -extension MultipartRawPartsToFramesSequence.StateMachine.State { - var isInitial: Bool { - guard case .initial = self else { return false } - return true - } - var isWaitingForPart: Bool { - guard case .waitingForPart = self else { return false } - return true - } - var isStreamingBody: Bool { - guard case .streamingBody = self else { return false } - return true - } - var isFinished: Bool { - guard case .finished = self else { return false } - return true - } -} - -extension MultipartRawPartsToFramesSequence.StateMachine.NextAction { - var isReturnNil: Bool { - guard case .returnNil = self else { return false } - return true - } - var isFetchPart: Bool { - guard case .fetchPart = self else { return false } - return true - } - var isFetchBodyChunk: Bool { - guard case .fetchBodyChunk = self else { return false } - return true - } -} diff --git a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift index f5b53a8f..ac3b5c55 100644 --- a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift +++ b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift @@ -66,25 +66,24 @@ class Test_Runtime: XCTestCase { var bytes: [UInt8] = [] bytes.append(contentsOf: "--__X_SWIFT_OPENAPI_GENERATOR_BOUNDARY__".utf8) bytes.append(contentsOf: ASCII.crlf) - bytes.append(contentsOf: #"content-disposition: form-data; filename="foo.txt"; name="hello""#.utf8) + bytes.append(contentsOf: #"Content-Disposition: form-data; filename="foo.txt"; name="hello""#.utf8) bytes.append(contentsOf: ASCII.crlf) - bytes.append(contentsOf: #"content-length: 5"#.utf8) + bytes.append(contentsOf: #"Content-Length: 5"#.utf8) bytes.append(contentsOf: ASCII.crlf) bytes.append(contentsOf: ASCII.crlf) bytes.append(contentsOf: "hello".utf8) bytes.append(contentsOf: ASCII.crlf) bytes.append(contentsOf: "--__X_SWIFT_OPENAPI_GENERATOR_BOUNDARY__".utf8) bytes.append(contentsOf: ASCII.crlf) - bytes.append(contentsOf: #"content-disposition: form-data; filename="bar.txt"; name="world""#.utf8) + bytes.append(contentsOf: #"Content-Disposition: form-data; filename="bar.txt"; name="world""#.utf8) bytes.append(contentsOf: ASCII.crlf) - bytes.append(contentsOf: #"content-length: 5"#.utf8) + bytes.append(contentsOf: #"Content-Length: 5"#.utf8) bytes.append(contentsOf: ASCII.crlf) bytes.append(contentsOf: ASCII.crlf) bytes.append(contentsOf: "world".utf8) bytes.append(contentsOf: ASCII.crlf) bytes.append(contentsOf: "--__X_SWIFT_OPENAPI_GENERATOR_BOUNDARY__--".utf8) bytes.append(contentsOf: ASCII.crlf) - bytes.append(contentsOf: ASCII.crlf) return ArraySlice(bytes) }