diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 66e7656..60443f4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,12 +11,7 @@ jobs: name: Unit tests uses: apple/swift-nio/.github/workflows/unit_tests.yml@main with: - linux_5_10_enabled: false - linux_6_0_enabled: false - linux_6_1_enabled: false - linux_6_2_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_6_3_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_next_enabled: false linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" windows_6_0_enabled: false windows_6_1_enabled: false @@ -33,6 +28,7 @@ jobs: xcode_16_4_enabled: false xcode_26_0_enabled: false xcode_26_1_enabled: false + xcode_26_2_enabled: false macos_xcode_build_enabled: false ios_xcode_build_enabled: false watchos_xcode_build_enabled: false @@ -45,6 +41,7 @@ jobs: linux_5_10_enabled: false linux_6_0_enabled: false linux_6_1_enabled: false + linux_nightly_next_enabled: false windows_6_0_enabled: false windows_6_1_enabled: false windows_nightly_next_enabled: false diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 0ffcb1e..1330536 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,18 +10,15 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: license_header_check_project_name: "Swift HTTP API Proposal" + api_breakage_check_container_image: "swiftlang/swift:nightly-main-noble" format_check_container_image: "swiftlang/swift:nightly-main-noble" # Needed due to https://github.com/swiftlang/swift-format/issues/1081 unit-tests: name: Unit tests uses: apple/swift-nio/.github/workflows/unit_tests.yml@main with: - linux_6_2_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_6_3_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_next_enabled: false linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - windows_6_0_enabled: false - windows_6_1_enabled: false windows_nightly_6_1_enabled: false windows_nightly_main_enabled: false @@ -35,6 +32,7 @@ jobs: xcode_16_4_enabled: false xcode_26_0_enabled: false xcode_26_1_enabled: false + xcode_26_2_enabled: false macos_xcode_build_enabled: false ios_xcode_build_enabled: false watchos_xcode_build_enabled: false @@ -47,6 +45,7 @@ jobs: linux_5_10_enabled: false linux_6_0_enabled: false linux_6_1_enabled: false + linux_nightly_next_enabled: false windows_6_0_enabled: false windows_6_1_enabled: false windows_nightly_next_enabled: false diff --git a/.gitignore b/.gitignore index 9563991..ef48e22 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ target/ Package.resolved /.vscode/ /.vstags +Package.resolved diff --git a/Package.swift b/Package.swift index 2446fdd..e0c3ba4 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,10 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.4 import PackageDescription let extraSettings: [SwiftSetting] = [ .strictMemorySafety(), - .enableExperimentalFeature("SuppressedAssociatedTypes"), + .enableExperimentalFeature("SuppressedAssociatedTypesWithDefaults"), .enableExperimentalFeature("LifetimeDependence"), .enableExperimentalFeature("Lifetimes"), .enableUpcomingFeature("LifetimeDependence"), diff --git a/README.md b/README.md index 2a4afda..68fccff 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ concurrency—to provide modern, safe, and efficient HTTP abstractions. final—we're iterating on designs based on feedback and real-world usage. The APIs and structure will continue to evolve as we refine the approach. +> [!IMPORTANT] +> This repository requires a Swift 6.4 aligned toolchain. + ## Motivation The Swift ecosystem currently lacks standardized, modern, and cross-platform diff --git a/Sources/AsyncStreaming/EitherError.swift b/Sources/AsyncStreaming/EitherError.swift index 9357eaa..f7ede13 100644 --- a/Sources/AsyncStreaming/EitherError.swift +++ b/Sources/AsyncStreaming/EitherError.swift @@ -53,3 +53,6 @@ public enum EitherError: Error { } } } + +extension EitherError: Equatable where First: Equatable, Second: Equatable {} +extension EitherError: Hashable where First: Hashable, Second: Hashable {} diff --git a/Sources/AsyncStreaming/Reader/AsyncReader+forEach.swift b/Sources/AsyncStreaming/Reader/AsyncReader+forEach.swift index 391f0fb..4f8cc8f 100644 --- a/Sources/AsyncStreaming/Reader/AsyncReader+forEach.swift +++ b/Sources/AsyncStreaming/Reader/AsyncReader+forEach.swift @@ -77,27 +77,22 @@ extension AsyncReader where Self: ~Copyable, Self: ~Escapable { /// } /// ``` @inlinable - public consuming func forEach( - body: (consuming Span) async throws(Failure) -> Void - ) async throws(Failure) where ReadFailure == Never { + public consuming func forEach( + body: (consuming Span) async -> Void + ) async where ReadFailure == Never { var shouldContinue = true while shouldContinue { do { - try await self.read(maximumCount: nil) { (next) throws(Failure) -> Void in + try await self.read(maximumCount: nil) { (next) -> Void in guard next.count > 0 else { shouldContinue = false return } - try await body(next) + await body(next) } } catch { - switch error { - case .first: - fatalError() - case .second(let error): - throw error - } + fatalError() } } } diff --git a/Sources/AsyncStreaming/Reader/ConcludingAsyncReader+collect.swift b/Sources/AsyncStreaming/Reader/ConcludingAsyncReader+collect.swift index 7f93d3d..c0076b0 100644 --- a/Sources/AsyncStreaming/Reader/ConcludingAsyncReader+collect.swift +++ b/Sources/AsyncStreaming/Reader/ConcludingAsyncReader+collect.swift @@ -12,7 +12,7 @@ //===----------------------------------------------------------------------===// @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -extension ConcludingAsyncReader where Self: ~Copyable { +extension ConcludingAsyncReader where Self: ~Copyable, Underlying: ~Copyable { /// Collects elements from the underlying async reader and returns both the processed result and final element. /// /// This method provides a convenient way to collect elements from the underlying reader while diff --git a/Sources/AsyncStreaming/Writer/ConcludingAsyncWriter.swift b/Sources/AsyncStreaming/Writer/ConcludingAsyncWriter.swift index c553480..c08d28f 100644 --- a/Sources/AsyncStreaming/Writer/ConcludingAsyncWriter.swift +++ b/Sources/AsyncStreaming/Writer/ConcludingAsyncWriter.swift @@ -48,7 +48,7 @@ public protocol ConcludingAsyncWriter: ~Copyable, ~Esc } @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -extension ConcludingAsyncWriter where Self: ~Copyable { +extension ConcludingAsyncWriter where Self: ~Copyable, Underlying: ~Copyable { /// Produces a final element using the underlying async writer without returning a separate value. /// /// This is a convenience method for cases where you only need to produce a final element @@ -80,7 +80,7 @@ extension ConcludingAsyncWriter where Self: ~Copyable { } @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -extension ConcludingAsyncWriter where Self: ~Copyable { +extension ConcludingAsyncWriter where Self: ~Copyable, Underlying: ~Copyable { /// Writes a single element to the underlying writer and concludes with a final element. /// /// This is a convenience method for simple scenarios where you need to write exactly one diff --git a/Sources/HTTPAPIs/Client/HTTPClient+Conveniences.swift b/Sources/HTTPAPIs/Client/HTTPClient+Conveniences.swift index 96bca5f..394b429 100644 --- a/Sources/HTTPAPIs/Client/HTTPClient+Conveniences.swift +++ b/Sources/HTTPAPIs/Client/HTTPClient+Conveniences.swift @@ -20,7 +20,13 @@ public import struct Foundation.Data #endif @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -extension HTTPClient where Self: ~Copyable & ~Escapable { +extension HTTPClient +where + Self: ~Copyable & ~Escapable, + ResponseConcludingReader: ~Copyable, + ResponseConcludingReader.Underlying: ~Copyable, + RequestWriter: ~Copyable +{ /// Performs an HTTP request and processes the response. /// /// This convenience method provides default values for `body` and `options` arguments, @@ -223,7 +229,7 @@ extension HTTPClient where Self: ~Copyable & ~Escapable { } private static func collectBody(_ body: consuming Reader, upTo limit: Int) async throws -> Data - where Reader: ~Copyable, Reader.Underlying.ReadElement == UInt8 { + where Reader: ~Copyable, Reader.Underlying: ~Copyable, Reader.Underlying.ReadElement == UInt8 { try await body.collect(upTo: limit == .max ? .max : limit + 1) { if $0.count > limit { throw LengthLimitExceededError() diff --git a/Sources/HTTPAPIs/Client/HTTPClient.swift b/Sources/HTTPAPIs/Client/HTTPClient.swift index 62edf57..61d0196 100644 --- a/Sources/HTTPAPIs/Client/HTTPClient.swift +++ b/Sources/HTTPAPIs/Client/HTTPClient.swift @@ -27,7 +27,10 @@ public protocol HTTPClient: Sendable, ~Copyable, ~Escapable { /// The type used to read response body data and trailers. // TODO: Check if we should allow ~Escapable writers https://github.com/apple/swift-http-api-proposal/issues/13 associatedtype ResponseConcludingReader: ConcludingAsyncReader, ~Copyable, SendableMetatype - where ResponseConcludingReader.Underlying.ReadElement == UInt8, ResponseConcludingReader.FinalElement == HTTPFields? + where + ResponseConcludingReader.Underlying: ~Copyable, + ResponseConcludingReader.Underlying.ReadElement == UInt8, + ResponseConcludingReader.FinalElement == HTTPFields? /// The default request options for `perform`. var defaultRequestOptions: RequestOptions { get } diff --git a/Sources/HTTPAPIs/Server/HTTPServer.swift b/Sources/HTTPAPIs/Server/HTTPServer.swift index 9cfd197..0679a2a 100644 --- a/Sources/HTTPAPIs/Server/HTTPServer.swift +++ b/Sources/HTTPAPIs/Server/HTTPServer.swift @@ -20,12 +20,18 @@ public protocol HTTPServer: S /// The type used to read request body data and trailers. // TODO: Check if we should allow ~Escapable readers https://github.com/apple/swift-http-api-proposal/issues/13 associatedtype RequestConcludingReader: ConcludingAsyncReader, ~Copyable, SendableMetatype - where RequestConcludingReader.Underlying.ReadElement == UInt8, RequestConcludingReader.FinalElement == HTTPFields? + where + RequestConcludingReader.Underlying: ~Copyable, + RequestConcludingReader.Underlying.ReadElement == UInt8, + RequestConcludingReader.FinalElement == HTTPFields? /// The type used to write response body data and trailers. // TODO: Check if we should allow ~Escapable writers https://github.com/apple/swift-http-api-proposal/issues/13 associatedtype ResponseConcludingWriter: ConcludingAsyncWriter, ~Copyable, SendableMetatype - where ResponseConcludingWriter.Underlying.WriteElement == UInt8, ResponseConcludingWriter.FinalElement == HTTPFields? + where + ResponseConcludingWriter.Underlying: ~Copyable, + ResponseConcludingWriter.Underlying.WriteElement == UInt8, + ResponseConcludingWriter.FinalElement == HTTPFields? /// Starts an HTTP server with the specified request handler. /// @@ -44,5 +50,10 @@ public protocol HTTPServer: S /// let server = // create an instance of a type conforming to the `ServerProtocol` /// try await server.serve(handler: YourRequestHandler()) /// ``` - func serve(handler: some HTTPServerRequestHandler) async throws + func serve(handler: Handler) async throws + where + Handler.RequestReader == RequestConcludingReader, + Handler.RequestReader: ~Copyable, + Handler.ResponseWriter == ResponseConcludingWriter, + Handler.ResponseWriter: ~Copyable } diff --git a/Sources/HTTPAPIs/Server/HTTPServerClosureRequestHandler.swift b/Sources/HTTPAPIs/Server/HTTPServerClosureRequestHandler.swift index c9b9adc..dbe5e44 100644 --- a/Sources/HTTPAPIs/Server/HTTPServerClosureRequestHandler.swift +++ b/Sources/HTTPAPIs/Server/HTTPServerClosureRequestHandler.swift @@ -40,6 +40,8 @@ public struct HTTPServerClosureRequestHandler< ResponseWriter: ConcludingAsyncWriter & ~Copyable, >: HTTPServerRequestHandler where + RequestReader.Underlying: ~Copyable, + ResponseWriter.Underlying: ~Copyable, RequestReader.Underlying.ReadElement == UInt8, ResponseWriter.Underlying.WriteElement == UInt8, RequestReader.FinalElement == HTTPFields?, @@ -91,7 +93,15 @@ where } @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -extension HTTPServer where Self: ~Copyable, Self: ~Escapable { +extension HTTPServer +where + Self: ~Copyable, + Self: ~Escapable, + RequestConcludingReader: ~Copyable, + RequestConcludingReader.Underlying: ~Copyable, + ResponseConcludingWriter: ~Copyable, + ResponseConcludingWriter.Underlying: ~Copyable +{ /// Starts an HTTP server with a closure-based request handler. /// /// This method provides a convenient way to start an HTTP server using a closure to handle incoming requests. diff --git a/Sources/HTTPAPIs/Server/HTTPServerRequestHandler.swift b/Sources/HTTPAPIs/Server/HTTPServerRequestHandler.swift index b96aa17..28e3c92 100644 --- a/Sources/HTTPAPIs/Server/HTTPServerRequestHandler.swift +++ b/Sources/HTTPAPIs/Server/HTTPServerRequestHandler.swift @@ -58,11 +58,11 @@ public protocol HTTPServerRequestHandler: Sendable { /// The type used to read request body data and trailers. associatedtype RequestReader: ConcludingAsyncReader, ~Copyable - where RequestReader.Underlying.ReadElement == UInt8, RequestReader.FinalElement == HTTPFields? + where RequestReader.Underlying: ~Copyable, RequestReader.Underlying.ReadElement == UInt8, RequestReader.FinalElement == HTTPFields? /// The type used to write response body data and trailers. associatedtype ResponseWriter: ConcludingAsyncWriter, ~Copyable - where ResponseWriter.Underlying.WriteElement == UInt8, ResponseWriter.FinalElement == HTTPFields? + where ResponseWriter.Underlying: ~Copyable, ResponseWriter.Underlying.WriteElement == UInt8, ResponseWriter.FinalElement == HTTPFields? /// Handles an incoming HTTP request and generates a response. /// diff --git a/Sources/HTTPAPIs/Server/HTTPServerResponseSender.swift b/Sources/HTTPAPIs/Server/HTTPServerResponseSender.swift index 119960d..7c4289b 100644 --- a/Sources/HTTPAPIs/Server/HTTPServerResponseSender.swift +++ b/Sources/HTTPAPIs/Server/HTTPServerResponseSender.swift @@ -19,7 +19,7 @@ /// enforces proper HTTP semantics: exactly one non-informational response, followed by /// optional response body streaming and trailers. @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -public struct HTTPResponseSender: ~Copyable { +public struct HTTPResponseSender: ~Copyable where ResponseWriter.Underlying: ~Copyable { private let _sendInformational: (HTTPResponse) async throws -> Void private let _send: (HTTPResponse) async throws -> ResponseWriter diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPRequestConcludingAsyncReader.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPRequestConcludingAsyncReader.swift index 0844992..00ce7e1 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPRequestConcludingAsyncReader.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPRequestConcludingAsyncReader.swift @@ -114,7 +114,7 @@ public struct HTTPRequestConcludingAsyncReader: ConcludingAsyncReader, ~Copyable /// The type of errors that can occur during reading operations. public typealias Failure = any Error - private var iterator: NIOAsyncChannelInboundStream.AsyncIterator? + private var iterator: Disconnected.AsyncIterator?> internal var state: ReaderState @@ -125,7 +125,7 @@ public struct HTTPRequestConcludingAsyncReader: ConcludingAsyncReader, ~Copyable iterator: consuming sending NIOAsyncChannelInboundStream.AsyncIterator, readerState: ReaderState ) { - self.iterator = iterator + self.iterator = .init(value: iterator) self.state = readerState } @@ -157,7 +157,7 @@ public struct HTTPRequestConcludingAsyncReader: ConcludingAsyncReader, ~Copyable public consuming func consumeAndConclude( body: nonisolated(nonsending) (consuming sending RequestBodyAsyncReader) async throws(Failure) -> Return ) async throws(Failure) -> (Return, HTTPFields?) { - if let iterator = self.iterator.sendingTake() { + if let iterator = self.iterator.take() { let partsReader = RequestBodyAsyncReader(iterator: iterator, readerState: self.state) let result = try await body(partsReader) let trailers = self.state.wrapped.withLock { $0.trailers } @@ -174,10 +174,27 @@ extension HTTPRequestConcludingAsyncReader: Sendable {} @available(*, unavailable) extension HTTPRequestConcludingAsyncReader.RequestBodyAsyncReader: Sendable {} -extension Optional { - mutating func sendingTake() -> sending Self { - let result = consume self - self = nil - return result +@usableFromInline +struct Disconnected: ~Copyable, Sendable { + // This is safe since we take the value as sending and take consumes it + // and returns it as sending. + private nonisolated(unsafe) var value: Value? + + @usableFromInline + init(value: consuming sending Value) { + unsafe self.value = .some(value) + } + + @usableFromInline + consuming func take() -> sending Value { + nonisolated(unsafe) let value = unsafe self.value.take()! + return unsafe value + } + + @usableFromInline + mutating func swap(newValue: consuming sending Value) -> sending Value { + nonisolated(unsafe) let value = unsafe self.value.take()! + unsafe self.value = consume newValue + return unsafe value } } diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPResponseSender.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPResponseSender.swift index 4fb3e87..60a9e00 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPResponseSender.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPResponseSender.swift @@ -24,7 +24,8 @@ public import HTTPTypes /// This forces structure in the response flow, requiring users to send a single response before they can stream a response body and /// trailers using the returned `ResponseWriter`. @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -public struct HTTPResponseSender: ~Copyable { +public struct HTTPResponseSender: ~Copyable +where ResponseWriter.Underlying: ~Copyable & ~Escapable { private let _sendInformational: (HTTPResponse) async throws -> Void private let _send: (HTTPResponse) async throws -> ResponseWriter diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServer.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServer.swift index c19c726..db89875 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServer.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServer.swift @@ -25,6 +25,7 @@ public protocol HTTPServer: Sendable, ~Copyable, ~Escapable { /// `ReadElement`. associatedtype RequestReader: ConcludingAsyncReader & ~Copyable & SendableMetatype where + RequestReader.Underlying: ~Copyable, RequestReader.Underlying.ReadElement == UInt8, RequestReader.Underlying.ReadFailure == any Error, RequestReader.FinalElement == HTTPFields? @@ -34,6 +35,7 @@ public protocol HTTPServer: Sendable, ~Copyable, ~Escapable { /// `WriteElement`. associatedtype ResponseWriter: ConcludingAsyncWriter & ~Copyable & SendableMetatype where + ResponseWriter.Underlying: ~Copyable, ResponseWriter.Underlying.WriteElement == UInt8, ResponseWriter.Underlying.WriteFailure == any Error, ResponseWriter.FinalElement == HTTPFields? @@ -55,5 +57,12 @@ public protocol HTTPServer: Sendable, ~Copyable, ~Escapable { /// let server = // create an instance of a type conforming to the `HTTPServer` protocol /// try await server.serve(handler: YourRequestHandler()) /// ``` - func serve(handler: some HTTPServerRequestHandler) async throws + func serve( + handler: RequestHandler + ) async throws + where + RequestHandler.RequestReader == RequestReader, + RequestHandler.ResponseWriter == ResponseWriter, + RequestHandler.RequestReader: ~Copyable, + RequestHandler.ResponseWriter: ~Copyable } diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerClosureRequestHandler.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerClosureRequestHandler.swift index 0fa0678..3c22f5e 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerClosureRequestHandler.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerClosureRequestHandler.swift @@ -40,10 +40,13 @@ public import HTTPTypes @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) public struct HTTPServerClosureRequestHandler< ConcludingRequestReader: ConcludingAsyncReader & ~Copyable & SendableMetatype, - RequestReader: AsyncReader & ~Copyable & ~Escapable, + RequestReader: AsyncReader & ~Copyable, ConcludingResponseWriter: ConcludingAsyncWriter & ~Copyable & SendableMetatype, - RequestWriter: AsyncWriter & ~Copyable & ~Escapable + RequestWriter: AsyncWriter & ~Copyable >: HTTPServerRequestHandler { + public typealias ResponseWriter = ConcludingResponseWriter + public typealias RequestReader = ConcludingRequestReader + /// The underlying closure that handles HTTP requests. private let _handler: nonisolated(nonsending) @Sendable ( @@ -89,7 +92,7 @@ public struct HTTPServerClosureRequestHandler< } @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -extension HTTPServer { +extension HTTPServer where RequestReader.Underlying: Escapable, ResponseWriter.Underlying: Escapable { /// Starts an HTTP server with a closure-based request handler. /// /// This method provides a convenient way to start an HTTP server using a closure to handle incoming requests. diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerRequestHandler.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerRequestHandler.swift index a6b4c39..f617f4a 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerRequestHandler.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/HTTPServerRequestHandler.swift @@ -77,6 +77,7 @@ public protocol HTTPServerRequestHandler: Sendabl /// `ReadElement`. associatedtype RequestReader: ConcludingAsyncReader & ~Copyable & SendableMetatype where + RequestReader.Underlying: ~Copyable, RequestReader.Underlying.ReadElement == UInt8, RequestReader.FinalElement == HTTPFields? @@ -85,6 +86,7 @@ public protocol HTTPServerRequestHandler: Sendabl /// `WriteElement`. associatedtype ResponseWriter: ConcludingAsyncWriter & ~Copyable & SendableMetatype where + ResponseWriter.Underlying: ~Copyable, ResponseWriter.Underlying.WriteElement == UInt8, ResponseWriter.FinalElement == HTTPFields? diff --git a/Sources/Middleware/ChainedMiddleware.swift b/Sources/Middleware/ChainedMiddleware.swift index e070d50..2fdf03e 100644 --- a/Sources/Middleware/ChainedMiddleware.swift +++ b/Sources/Middleware/ChainedMiddleware.swift @@ -19,7 +19,9 @@ /// /// This type is primarily used internally by the ``MiddlewareChainBuilder`` to combine /// middleware components in a type-safe way. -struct ChainedMiddleware: Middleware where First.NextInput == Second.Input { +// TODO: Revisit if this type should be public +public struct ChainedMiddleware: Middleware +where First.Input: ~Copyable, First.NextInput: ~Copyable, Second.NextInput: ~Copyable, First.NextInput == Second.Input { /// The first middleware in the chain. private let first: First @@ -45,7 +47,7 @@ struct ChainedMiddleware: Middleware wher /// - next: The next handler function to call after both middlewares have processed the input. /// /// - Throws: Any error that occurs during processing in either middleware. - func intercept( + public func intercept( input: consuming First.Input, next: (consuming Second.NextInput) async throws -> Void ) async throws { diff --git a/Sources/Middleware/MiddlewareBuilder.swift b/Sources/Middleware/MiddlewareBuilder.swift index 9521d72..9bb2753 100644 --- a/Sources/Middleware/MiddlewareBuilder.swift +++ b/Sources/Middleware/MiddlewareBuilder.swift @@ -56,12 +56,15 @@ public struct MiddlewareBuilder { /// - accumulated: The first middleware in the chain. /// - next: The second middleware in the chain, which accepts the output of the first. /// - Returns: A new middleware chain that represents the composition of both middlewares. - public static func buildPartialBlock( - accumulated: some Middleware, - next: some Middleware - ) -> some Middleware { - let chained = ChainedMiddleware(first: accumulated, second: next) - return ClosureMiddleware(middlewareFunc: chained.intercept) + public static func buildPartialBlock< + First: Middleware, + Second: Middleware + >( + accumulated: First, + next: Second + ) -> ChainedMiddleware + where First.Input: ~Copyable, First.NextInput: ~Copyable, Second.NextInput: ~Copyable, First.NextInput == Second.Input { + return ChainedMiddleware(first: accumulated, second: next) } /// Converts a middleware expression to a middleware chain. diff --git a/Tests/AsyncStreamingTests/Reader/AsyncReader+forEachTests.swift b/Tests/AsyncStreamingTests/Reader/AsyncReader+forEachTests.swift index bc078f8..8695d27 100644 --- a/Tests/AsyncStreamingTests/Reader/AsyncReader+forEachTests.swift +++ b/Tests/AsyncStreamingTests/Reader/AsyncReader+forEachTests.swift @@ -72,7 +72,7 @@ struct AsyncReaderForEachTests { } Issue.record("Expected error to be thrown") } catch { - #expect(error == TestError.failed) + #expect(error == EitherError.second(TestError.failed)) } } diff --git a/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift b/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift index f996dd4..bd0e98b 100644 --- a/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift +++ b/Tests/HTTPAPIsTests/Helpers/HTTPClientAndServerTests.swift @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -import AsyncAlgorithms +public import AsyncAlgorithms // TODO: This public import is only needed to work around a compiler assertion which is fixed by https://github.com/swiftlang/swift/pull/88829 import AsyncStreaming import BasicContainers import HTTPAPIs