diff --git a/.github/workflows/swift6-compatibility.yml b/.github/workflows/swift6-compatibility.yml new file mode 100644 index 000000000..756dffb5f --- /dev/null +++ b/.github/workflows/swift6-compatibility.yml @@ -0,0 +1,34 @@ +name: Swift6 Compatibility + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +env: + AWS_SWIFT_SDK_USE_LOCAL_DEPS: 1 + +jobs: + bump-swift-tools-version: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + runner: + - macos-15 + xcode: + - Xcode_16.1 + destination: + - 'platform=macOS' + steps: + - name: Checkout smithy-swift + uses: actions/checkout@v4 + - name: Setup common tools + uses: ./.github/actions/setup-common-tools-composite-action + - name: Build on swift-tools-version 6.0 + run: | + perl -pi -e 's{^// swift-tools-version:\d+\.\d+}{// swift-tools-version:6.0}' Package.swift + set -euo pipefail + swift build diff --git a/Package.swift b/Package.swift index f878e6f40..79d3e9121 100644 --- a/Package.swift +++ b/Package.swift @@ -56,7 +56,7 @@ let package = Package( ], dependencies: { var dependencies: [Package.Dependency] = [ - .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.48.0"), + .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.52.1"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), ] let isDocCEnabled = ProcessInfo.processInfo.environment["AWS_SWIFT_SDK_ENABLE_DOCC"] != nil @@ -157,7 +157,10 @@ let package = Package( ), .target( name: "SmithyHTTPAPI", - dependencies: ["Smithy"] + dependencies: [ + "Smithy", + .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift") + ] ), .target( name: "SmithyHTTPClient", diff --git a/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift b/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift index e50540a04..9d347b24f 100644 --- a/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift @@ -131,7 +131,7 @@ public class DefaultAuthSchemeResolverParameters: AuthSchemeResolverParameters { } } -public class DefaultAuthSchemeResolver: AuthSchemeResolver { +public final class DefaultAuthSchemeResolver: AuthSchemeResolver, Sendable { public func resolveAuthScheme(params: AuthSchemeResolverParameters) throws -> [AuthOption] { return [] } diff --git a/Sources/ClientRuntime/Config/IdempotencyTokenGenerator.swift b/Sources/ClientRuntime/Config/IdempotencyTokenGenerator.swift index 8510cd742..ea790169a 100644 --- a/Sources/ClientRuntime/Config/IdempotencyTokenGenerator.swift +++ b/Sources/ClientRuntime/Config/IdempotencyTokenGenerator.swift @@ -3,6 +3,6 @@ * SPDX-License-Identifier: Apache-2.0. */ -public protocol IdempotencyTokenGenerator { +public protocol IdempotencyTokenGenerator: Sendable { func generateToken() -> String } diff --git a/Sources/ClientRuntime/Endpoints/CRTResolvedEndpoint.swift b/Sources/ClientRuntime/Endpoints/CRTResolvedEndpoint.swift index 4f6fbe6df..d718d09b7 100644 --- a/Sources/ClientRuntime/Endpoints/CRTResolvedEndpoint.swift +++ b/Sources/ClientRuntime/Endpoints/CRTResolvedEndpoint.swift @@ -34,7 +34,7 @@ public class CRTResolvedEndpoint { } } - public func getProperties() -> [String: AnyHashable]? { + public func getProperties() -> [String: EndpointProperty]? { switch crtResolvedEndpoint { case let .endpoint(_, _, properties): return properties diff --git a/Sources/ClientRuntime/Endpoints/DefaultEndpointResolver.swift b/Sources/ClientRuntime/Endpoints/DefaultEndpointResolver.swift index e33fea035..68423c024 100644 --- a/Sources/ClientRuntime/Endpoints/DefaultEndpointResolver.swift +++ b/Sources/ClientRuntime/Endpoints/DefaultEndpointResolver.swift @@ -7,6 +7,7 @@ import struct SmithyHTTPAPI.Endpoint import struct SmithyHTTPAPI.Headers +import enum SmithyHTTPAPI.EndpointPropertyValue public struct DefaultEndpointResolver { @@ -32,6 +33,6 @@ public struct DefaultEndpointResolver let headers = crtResolvedEndpoint.getHeaders() ?? [:] let properties = crtResolvedEndpoint.getProperties() ?? [:] - return try Endpoint(urlString: url, headers: Headers(headers), properties: properties) + return try Endpoint(urlString: url, headers: Headers(headers), endpointProperties: properties) } } diff --git a/Sources/ClientRuntime/Endpoints/EndpointsAuthSchemeResolver.swift b/Sources/ClientRuntime/Endpoints/EndpointsAuthSchemeResolver.swift index fb604bb38..7fab1ce0c 100644 --- a/Sources/ClientRuntime/Endpoints/EndpointsAuthSchemeResolver.swift +++ b/Sources/ClientRuntime/Endpoints/EndpointsAuthSchemeResolver.swift @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // +import enum SmithyHTTPAPI.EndpointPropertyValue + /// Supported authentication schemes public enum EndpointsAuthScheme: Equatable { case sigV4(SigV4Parameters) @@ -25,7 +27,8 @@ extension EndpointsAuthScheme { /// Initialize an AuthScheme from a dictionary /// - Parameter dictionary: Dictionary containing the auth scheme public init(from dictionary: [String: Any]) throws { - guard let name = dictionary["name"] as? String else { + guard let endpointProperty = dictionary["name"] as? SmithyHTTPAPI.EndpointPropertyValue, + case let .string(name) = endpointProperty else { throw EndpointError.authScheme("Invalid auth scheme") } switch name { @@ -60,9 +63,29 @@ extension EndpointsAuthScheme.SigV4Parameters { /// Initialize a SigV4AuthScheme from a dictionary /// - Parameter dictionary: Dictionary containing the auth scheme init(from dictionary: [String: Any]) throws { - self.signingName = dictionary["signingName"] as? String - self.signingRegion = dictionary["signingRegion"] as? String - self.disableDoubleEncoding = dictionary["disableDoubleEncoding"] as? Bool + // For signingName (expected to be a string) + if let value = dictionary["signingName"] as? EndpointPropertyValue, + case let .string(name) = value { + self.signingName = name + } else { + self.signingName = nil + } + + // For signingRegion (expected to be a string) + if let value = dictionary["signingRegion"] as? EndpointPropertyValue, + case let .string(region) = value { + self.signingRegion = region + } else { + self.signingRegion = nil + } + + // For disableDoubleEncoding (expected to be a bool) + if let value = dictionary["disableDoubleEncoding"] as? EndpointPropertyValue, + case let .bool(flag) = value { + self.disableDoubleEncoding = flag + } else { + self.disableDoubleEncoding = nil + } } } @@ -86,9 +109,34 @@ extension EndpointsAuthScheme.SigV4AParameters { /// Initialize a SigV4AAuthScheme from a dictionary /// - Parameter dictionary: Dictionary containing the auth scheme init(from dictionary: [String: Any]) throws { - self.signingName = dictionary["signingName"] as? String - self.signingRegionSet = dictionary["signingRegionSet"] as? [String] - self.disableDoubleEncoding = dictionary["disableDoubleEncoding"] as? Bool + // Extract signingName + if let value = dictionary["signingName"] as? EndpointPropertyValue, + case let .string(name) = value { + self.signingName = name + } else { + self.signingName = nil + } + + // Extract signingRegionSet as an array of String + if let value = dictionary["signingRegionSet"] as? EndpointPropertyValue, + case let .array(regions) = value { + self.signingRegionSet = regions.compactMap { element in + if case let .string(region) = element { + return region + } + return nil + } + } else { + self.signingRegionSet = nil + } + + // Extract disableDoubleEncoding + if let value = dictionary["disableDoubleEncoding"] as? EndpointPropertyValue, + case let .bool(flag) = value { + self.disableDoubleEncoding = flag + } else { + self.disableDoubleEncoding = nil + } } } diff --git a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift index 4c45e684d..e48d44b17 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift @@ -185,200 +185,58 @@ public class CRTClientEngine: HTTPClient { self.serialExecutor = SerialExecutor(config: config) } - // swiftlint:disable function_body_length public func send(request: HTTPRequest) async throws -> HTTPResponse { let telemetryContext = telemetry.contextManager.current() let tracer = telemetry.tracerProvider.getTracer( scope: telemetry.tracerScope, - attributes: telemetry.tracerAttributes) - do { - // START - smithy.client.http.requests.queued_duration - let queuedStart = Date().timeIntervalSinceReferenceDate - let span = tracer.createSpan( - name: telemetry.spanName, - initialAttributes: telemetry.spanAttributes, - spanKind: SpanKind.internal, - parentContext: telemetryContext) - defer { - span.end() - } - let connectionMgr = try await serialExecutor.getOrCreateConnectionPool(endpoint: request.endpoint) - - // START - smithy.client.http.connections.acquire_duration - let acquireConnectionStart = Date().timeIntervalSinceReferenceDate - let connection = try await connectionMgr.acquireConnection() - let acquireConnectionEnd = Date().timeIntervalSinceReferenceDate - telemetry.connectionsAcquireDuration.record( - value: acquireConnectionEnd - acquireConnectionStart, - attributes: Attributes(), - context: telemetryContext) - // END - smithy.client.http.connections.acquire_duration - let queuedEnd = acquireConnectionEnd - telemetry.requestsQueuedDuration.record( - value: queuedEnd - queuedStart, - attributes: Attributes(), - context: telemetryContext) - // END - smithy.client.http.requests.queued_duration - - let connectionsLimit = serialExecutor.maxConnectionsPerEndpoint - let connectionMgrMetrics = connectionMgr.fetchMetrics() - telemetry.updateHTTPMetricsUsage { httpMetricsUsage in - // TICK - smithy.client.http.connections.limit - httpMetricsUsage.connectionsLimit = connectionsLimit - - // TICK - smithy.client.http.connections.usage - httpMetricsUsage.idleConnections = connectionMgrMetrics.availableConcurrency - httpMetricsUsage.acquiredConnections = connectionMgrMetrics.leasedConcurrency - - // TICK - smithy.client.http.requests.usage - httpMetricsUsage.inflightRequests = connectionMgrMetrics.leasedConcurrency - httpMetricsUsage.queuedRequests = connectionMgrMetrics.pendingConcurrencyAcquires - } - - do { - // DURATION - smithy.client.http.connections.uptime - let connectionUptimeStart = acquireConnectionEnd - defer { - telemetry.connectionsUptime.record( - value: Date().timeIntervalSinceReferenceDate - connectionUptimeStart, - attributes: Attributes(), - context: telemetryContext) - } - // swiftlint:disable:next line_length - self.logger.debug("Connection was acquired to: \(String(describing: request.destination.url?.absoluteString))") - switch connection.httpVersion { - case .version_1_1: - self.logger.debug("Using HTTP/1.1 connection") - let crtRequest = try request.toHttpRequest() - return try await withCheckedThrowingContinuation { (continuation: StreamContinuation) in - let wrappedContinuation = ContinuationWrapper(continuation) - let requestOptions = makeHttpRequestStreamOptions( - request: crtRequest, - continuation: wrappedContinuation, - serverAddress: CRTClientEngine.makeServerAddress(request: request) - ) - do { - let stream = try connection.makeRequest(requestOptions: requestOptions) - try stream.activate() - if request.isChunked { - Task { - do { - guard let http1Stream = stream as? HTTP1Stream else { - throw StreamError.notSupported( - "HTTP1Stream should be used with an HTTP/1.1 connection!" - ) - } - let body = request.body - // swiftlint:disable line_length - guard case .stream(let stream) = body, stream.isEligibleForChunkedStreaming else { - throw ByteStreamError.invalidStreamTypeForChunkedBody( - "The stream is not eligible for chunked streaming or is not a stream type!" - ) - } - // swiftlint:enable line_length - - guard let chunkedStream = stream as? ChunkedStream else { - throw ByteStreamError.streamDoesNotConformToChunkedStream( - "Stream does not conform to ChunkedStream! Type is \(stream)." - ) - } - - var hasMoreChunks = true - var currentChunkBodyIsEmpty = false - while hasMoreChunks { - // Process the first chunk and determine if there are more to send - hasMoreChunks = try await chunkedStream.chunkedReader.processNextChunk() - currentChunkBodyIsEmpty = chunkedStream - .chunkedReader - .getCurrentChunkBody() - .isEmpty - - if !hasMoreChunks || currentChunkBodyIsEmpty { - // Send the final chunk - let finalChunk = try await chunkedStream.chunkedReader.getFinalChunk() - // TICK - smithy.client.http.bytes_sent - try await http1Stream.writeChunk(chunk: finalChunk, endOfStream: true) - var bytesSentAttributes = Attributes() - bytesSentAttributes.set( - key: HttpMetricsAttributesKeys.serverAddress, - value: CRTClientEngine.makeServerAddress(request: request)) - telemetry.bytesSent.add( - value: finalChunk.count, - attributes: bytesSentAttributes, - context: telemetry.contextManager.current()) - hasMoreChunks = false - } else { - let currentChunk = chunkedStream.chunkedReader.getCurrentChunk() - // TICK - smithy.client.http.bytes_sent - try await http1Stream.writeChunk( - chunk: currentChunk, - endOfStream: false - ) - var bytesSentAttributes = Attributes() - bytesSentAttributes.set( - key: HttpMetricsAttributesKeys.serverAddress, - value: CRTClientEngine.makeServerAddress(request: request)) - telemetry.bytesSent.add( - value: currentChunk.count, - attributes: bytesSentAttributes, - context: telemetry.contextManager.current()) - } - } - } catch { - logger.error(error.localizedDescription) - wrappedContinuation.safeResume(error: error) - } - } - } - } catch { - logger.error(error.localizedDescription) - wrappedContinuation.safeResume(error: error) - } - } - case .version_2: - self.logger.debug("Using HTTP/2 connection") - let crtRequest = try request.toHttp2Request() - return try await withCheckedThrowingContinuation { (continuation: StreamContinuation) in - let wrappedContinuation = ContinuationWrapper(continuation) - let requestOptions = makeHttpRequestStreamOptions( - request: crtRequest, - continuation: wrappedContinuation, - http2ManualDataWrites: true, - serverAddress: CRTClientEngine.makeServerAddress(request: request) - ) - let stream: HTTP2Stream - do { - // swiftlint:disable:next force_cast - stream = try connection.makeRequest(requestOptions: requestOptions) as! HTTP2Stream - try stream.activate() - } catch { - logger.error(error.localizedDescription) - wrappedContinuation.safeResume(error: error) - return - } + attributes: telemetry.tracerAttributes + ) + let queuedStart = Date().timeIntervalSinceReferenceDate + let span = tracer.createSpan( + name: telemetry.spanName, + initialAttributes: telemetry.spanAttributes, + spanKind: .internal, + parentContext: telemetryContext + ) + defer { + span.end() + } - // At this point, continuation is resumed when the initial headers are received - // it is now safe to write the body - // writing is done in a separate task to avoid blocking the continuation - Task { [logger] in - do { - // TICK - smithy.client.http.bytes_sent - try await stream.write( - body: request.body, - telemetry: telemetry, - serverAddress: CRTClientEngine.makeServerAddress(request: request)) - } catch { - logger.error(error.localizedDescription) - } - } - } - case .unknown: - fatalError("Unknown HTTP version") - } - } + let connectionMgr = try await serialExecutor.getOrCreateConnectionPool(endpoint: request.endpoint) + let acquireConnectionStart = Date().timeIntervalSinceReferenceDate + let connection = try await connectionMgr.acquireConnection() + let acquireConnectionEnd = Date().timeIntervalSinceReferenceDate + + telemetry.connectionsAcquireDuration.record( + value: acquireConnectionEnd - acquireConnectionStart, + attributes: Attributes(), + context: telemetryContext + ) + telemetry.requestsQueuedDuration.record( + value: acquireConnectionEnd - queuedStart, + attributes: Attributes(), + context: telemetryContext + ) + + updateHTTPMetrics(connectionMgr.fetchMetrics()) + + switch connection.httpVersion { + case .version_1_1: + return try await handleHTTP1Request( + connection: connection, + request: request, + telemetryContext: telemetryContext + ) + case .version_2: + return try await handleHTTP2Request( + connection: connection, + request: request, + telemetryContext: telemetryContext + ) + case .unknown: + fatalError("Unknown HTTP version") } } - // swiftlint:enable function_body_length // Forces an Http2 request that uses CRT's `HTTP2StreamManager`. // This may be removed or improved as part of SRA work and CRT adapting to SRA for HTTP. @@ -411,7 +269,10 @@ public class CRTClientEngine: HTTPClient { http2ManualDataWrites: true, serverAddress: CRTClientEngine.makeServerAddress(request: request) ) - Task { [logger] in + let telemetry = self.telemetry + let logger = self.logger + let serialExecutor = self.serialExecutor + Task { @Sendable in let stream: HTTP2Stream var acquireConnectionEnd: TimeInterval do { @@ -432,7 +293,7 @@ public class CRTClientEngine: HTTPClient { // END - smithy.client.http.requests.queued_duration } catch { logger.error(error.localizedDescription) - wrappedContinuation.safeResume(error: error) + wrappedContinuation.resume(throwing: error) return } @@ -477,6 +338,176 @@ public class CRTClientEngine: HTTPClient { } } + private func updateHTTPMetrics(_ metrics: HTTPClientConnectionManagerMetrics) { + let connectionsLimit = serialExecutor.maxConnectionsPerEndpoint + telemetry.updateHTTPMetricsUsage { httpMetricsUsage in + httpMetricsUsage.connectionsLimit = connectionsLimit + httpMetricsUsage.idleConnections = metrics.availableConcurrency + httpMetricsUsage.acquiredConnections = metrics.leasedConcurrency + httpMetricsUsage.inflightRequests = metrics.leasedConcurrency + httpMetricsUsage.queuedRequests = metrics.pendingConcurrencyAcquires + } + } + + private func handleHTTP1Request( + connection: HTTPClientConnection, + request: HTTPRequest, + telemetryContext: TelemetryContext + ) async throws -> HTTPResponse { + logger.debug("Using HTTP/1.1 connection") + let crtRequest = try request.toHttpRequest() + let serverAddress = Self.makeServerAddress(request: request) + + return try await withCheckedThrowingContinuation { [weak self] continuation in + guard let self = self else { + continuation.resume(throwing: StreamError.connectionReleased("Engine released")) + return + } + + let wrappedContinuation = ContinuationWrapper(continuation) + + do { + let requestOptions = self.makeHttpRequestStreamOptions( + request: crtRequest, + continuation: wrappedContinuation, + serverAddress: serverAddress + ) + + let stream = try connection.makeRequest(requestOptions: requestOptions) + try stream.activate() + + if request.isChunked { + let logger = self.logger + let telemetry = self.telemetry + + Task { @Sendable in + do { + + try await CRTClientEngine.sendChunkedBody( + using: stream, + request: request, + logger: logger, + telemetry: telemetry, + serverAddress: serverAddress + ) + } catch { + logger.error("Chunked streaming error: \(error.localizedDescription)") + wrappedContinuation.resume(throwing: error) + } + } + } + } catch { + self.logger.error("Request setup error: \(error.localizedDescription)") + wrappedContinuation.resume(throwing: error) + } + } + } + + private func handleHTTP2Request( + connection: HTTPClientConnection, + request: HTTPRequest, + telemetryContext: TelemetryContext + ) async throws -> HTTPResponse { + self.logger.debug("Using HTTP/2 connection") + let crtRequest = try request.toHttp2Request() + return try await withCheckedThrowingContinuation { (continuation: StreamContinuation) in + let wrappedContinuation = ContinuationWrapper(continuation) + let requestOptions = self.makeHttpRequestStreamOptions( + request: crtRequest, + continuation: wrappedContinuation, + http2ManualDataWrites: true, + serverAddress: CRTClientEngine.makeServerAddress(request: request) + ) + do { + guard let stream = try connection.makeRequest(requestOptions: requestOptions) as? HTTP2Stream else { + throw StreamError.notSupported("HTTP2Stream expected for HTTP/2 connection!") + } + try stream.activate() + + let telemetry = self.telemetry + let logger = self.logger + + Task { @Sendable in + do { + try await stream.write( + body: request.body, + telemetry: telemetry, + serverAddress: CRTClientEngine.makeServerAddress(request: request) + ) + } catch { + logger.error(error.localizedDescription) + } + } + } catch { + self.logger.error(error.localizedDescription) + wrappedContinuation.resume(throwing: error) + return + } + } + } + + private static func sendChunkedBody( + using stream: HTTPStream, + request: HTTPRequest, + logger: LogAgent, + telemetry: HttpTelemetry, + serverAddress: String + ) async throws { + guard let http1Stream = stream as? HTTP1Stream else { + throw StreamError.notSupported("HTTP1Stream should be used with an HTTP/1.1 connection!") + } + + let body = request.body + guard case .stream(let stream) = body, stream.isEligibleForChunkedStreaming else { + throw ByteStreamError.invalidStreamTypeForChunkedBody( + "The stream is not eligible for chunked streaming or is not a stream type!" + ) + } + + guard let chunkedStream = stream as? ChunkedStream else { + throw ByteStreamError.streamDoesNotConformToChunkedStream( + "Stream does not conform to ChunkedStream! Type is \(stream)." + ) + } + + var hasMoreChunks = true + while hasMoreChunks { + hasMoreChunks = try await chunkedStream.chunkedReader.processNextChunk() + let currentChunk = chunkedStream.chunkedReader.getCurrentChunk() + let currentChunkBodyIsEmpty = chunkedStream.chunkedReader.getCurrentChunkBody().isEmpty + + if !hasMoreChunks || currentChunkBodyIsEmpty { + let finalChunk = try await chunkedStream.chunkedReader.getFinalChunk() + try await http1Stream.writeChunk(chunk: finalChunk, endOfStream: true) + + var bytesSentAttributes = Attributes() + bytesSentAttributes.set( + key: HttpMetricsAttributesKeys.serverAddress, + value: serverAddress + ) + telemetry.bytesSent.add( + value: finalChunk.count, + attributes: bytesSentAttributes, + context: telemetry.contextManager.current() + ) + break + } else { + try await http1Stream.writeChunk(chunk: currentChunk, endOfStream: false) + + var bytesSentAttributes = Attributes() + bytesSentAttributes.set( + key: HttpMetricsAttributesKeys.serverAddress, + value: serverAddress + ) + telemetry.bytesSent.add( + value: currentChunk.count, + attributes: bytesSentAttributes, + context: telemetry.contextManager.current() + ) + } + } + } + /// Creates a `HTTPRequestOptions` object that can be used to make a HTTP request /// - Parameters: /// - request: The `HTTPRequestBase` object that contains the request information @@ -490,7 +521,7 @@ public class CRTClientEngine: HTTPClient { /// - Returns: A `HTTPRequestOptions` object that can be used to make a HTTP request private func makeHttpRequestStreamOptions( request: HTTPRequestBase, - continuation: ContinuationWrapper, + continuation: ContinuationWrapper, http2ManualDataWrites: Bool = false, serverAddress: String ) -> HTTPRequestOptions { @@ -516,7 +547,7 @@ public class CRTClientEngine: HTTPClient { // resume the continuation as soon as we have all the initial headers // this allows callers to start reading the response as it comes in // instead of waiting for the entire response to be received - continuation.safeResume(response: response) + continuation.resume(returning: response) } onIncomingBody: { bodyChunk in self.logger.debug("Body chunk received") do { @@ -558,21 +589,31 @@ public class CRTClientEngine: HTTPClient { } } - class ContinuationWrapper { - private var continuation: StreamContinuation? + // Guaranteed to be safe in concurrent environments due to use of locks + final class ContinuationWrapper: @unchecked Sendable { + + private var continuation: CheckedContinuation? + private let lock = NSLock() + + init(_ cont: CheckedContinuation) { + self.continuation = cont + } - public init(_ continuation: StreamContinuation) { - self.continuation = continuation + func resume(returning value: Response) { + take()?.resume(returning: value) } - public func safeResume(response: HTTPResponse) { - continuation?.resume(returning: response) - self.continuation = nil + func resume(throwing error: Error) { + take()?.resume(throwing: error) } - public func safeResume(error: Error) { - continuation?.resume(throwing: error) - self.continuation = nil + /// Fetch the continuation exactly once. Returns the current value under + /// a lock, then sets it to `nil` so later callers get nothing. + private func take() -> CheckedContinuation? { + lock.lock() + defer { continuation = nil; lock.unlock() } + let pendingContinuation = continuation + return pendingContinuation } } } diff --git a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift index 87130b5aa..25b13d4b8 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ -struct CRTClientEngineConfig { +struct CRTClientEngineConfig: Sendable { /// Max connections the manager can contain per endpoint let maxConnectionsPerEndpoint: Int diff --git a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientTLSOptions.swift b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientTLSOptions.swift index 623e243a7..8439830ff 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientTLSOptions.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientTLSOptions.swift @@ -8,7 +8,7 @@ import AwsCommonRuntimeKit import Smithy -public struct CRTClientTLSOptions: TLSConfiguration { +public struct CRTClientTLSOptions: TLSConfiguration, Sendable { /// Optional PEM certificate filename public var certificate: String? diff --git a/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift b/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift index f5e8ec020..f2a7cd79d 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift @@ -17,7 +17,8 @@ extension HTTP2Stream { } /// Writes the ByteStream to the stream asynchronously - /// There is no recommended size for the data to write. The data will be written in chunks of `manualWriteBufferSize` bytes. + /// There is no recommended size for the data to write. + /// The data will be written in chunks of `manualWriteBufferSize` bytes. /// - Parameter body: The body to write /// - Throws: Throws an error if the write fails func write(body: ByteStream, telemetry: HttpTelemetry, serverAddress: String) async throws { diff --git a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift index 6f49023f2..86a2ee1db 100644 --- a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift +++ b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift @@ -310,7 +310,7 @@ public final class URLSessionHTTPClient: HTTPClient { func urlSession( _ session: URLSession, task: URLSessionTask, - needNewBodyStream completionHandler: @escaping (InputStream?) -> Void + needNewBodyStream completionHandler: @escaping @Sendable (InputStream?) -> Void ) { storage.modify(task) { connection in guard let streamBridge = connection.streamBridge else { completionHandler(nil); return } @@ -362,6 +362,11 @@ public final class URLSessionHTTPClient: HTTPClient { // - The stream bridge is closed. // This ensures that resources are freed and stream readers/writers are continued. storage.modify(task) { connection in + let streamBridge = connection.streamBridge + let shouldRemove = { [self] in + storage.remove(task) + } + if let error = connection.error ?? error { if connection.hasBeenResumed { connection.responseStream.closeWithError(error) @@ -376,12 +381,9 @@ public final class URLSessionHTTPClient: HTTPClient { connection.responseStream.close() } - // Close the stream bridge so that its resources are deallocated Task { - await connection.streamBridge?.close() - - // Task is complete & no longer needed. Remove it from storage. - storage.remove(task) + await streamBridge?.close() + shouldRemove() } } } diff --git a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionTLSOptions.swift b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionTLSOptions.swift index a0553632a..ef89dd9ed 100644 --- a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionTLSOptions.swift +++ b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionTLSOptions.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public struct URLSessionTLSOptions: TLSConfiguration { +public struct URLSessionTLSOptions: TLSConfiguration, Sendable { /// Filename of the turst certificate file in main bundle (.cer) public var certificate: String? diff --git a/Sources/Smithy/Attribute.swift b/Sources/Smithy/Attribute.swift index 42cb8ea59..a082b54a2 100644 --- a/Sources/Smithy/Attribute.swift +++ b/Sources/Smithy/Attribute.swift @@ -19,21 +19,21 @@ public struct AttributeKey: Sendable { } /// Type safe property bag -public struct Attributes { - private var attributes = [String: Any]() +public struct Attributes: Sendable { + private var attributes = [String: any Sendable]() public var size: Int { attributes.count } public init() {} - public func get(key: AttributeKey) -> T? { + public func get(key: AttributeKey) -> T? { attributes[key.name] as? T } - public func contains(key: AttributeKey) -> Bool { + public func contains(key: AttributeKey) -> Bool { get(key: key) != nil } - public mutating func set(key: AttributeKey, value: T?) { + public mutating func set(key: AttributeKey, value: T?) { attributes[key.name] = value } diff --git a/Sources/Smithy/ByteStream.swift b/Sources/Smithy/ByteStream.swift index d6d00abf3..9a13b3338 100644 --- a/Sources/Smithy/ByteStream.swift +++ b/Sources/Smithy/ByteStream.swift @@ -73,7 +73,7 @@ extension ByteStream: CustomDebugStringConvertible { } } -public enum ByteStreamError: Error { +public enum ByteStreamError: Error, Sendable { case streamNotSeeakble case invalidStreamTypeForChunkedBody(String) case streamClosedOrEmpty diff --git a/Sources/Smithy/Context.swift b/Sources/Smithy/Context.swift index 175f92261..ed1f98317 100644 --- a/Sources/Smithy/Context.swift +++ b/Sources/Smithy/Context.swift @@ -7,7 +7,7 @@ import class Foundation.NSRecursiveLock -public class Context { +public final class Context: @unchecked Sendable { private var _attributes: Attributes private let lock = NSRecursiveLock() @@ -34,15 +34,15 @@ public class Context { extension Context { - public func get(key: AttributeKey) -> T? { + public func get(key: AttributeKey) -> T? { accessAttributes().get(key: key) } - public func contains(key: AttributeKey) -> Bool { + public func contains(key: AttributeKey) -> Bool { accessAttributes().contains(key: key) } - public func set(key: AttributeKey, value: T?) { + public func set(key: AttributeKey, value: T?) { accessAttributes { attributes in attributes.set(key: key, value: value) } @@ -68,7 +68,7 @@ public class ContextBuilder { // @discardableResult attribute we won't get warnings if we // don't end up doing any chaining. @discardableResult - public func with(key: AttributeKey, value: T) -> Self { + public func with(key: AttributeKey, value: T) -> Self { self.attributes.set(key: key, value: value) return self } diff --git a/Sources/Smithy/Stream.swift b/Sources/Smithy/Stream.swift index c16fda8f7..43a351812 100644 --- a/Sources/Smithy/Stream.swift +++ b/Sources/Smithy/Stream.swift @@ -60,9 +60,10 @@ public protocol WriteableStream: AnyObject, Sendable { public protocol Stream: ReadableStream, WriteableStream { } -public enum StreamError: Error { +public enum StreamError: Error, Sendable { case invalidOffset(String) case notSupported(String) + case connectionReleased(String) } extension Stream { diff --git a/Sources/Smithy/URI.swift b/Sources/Smithy/URI.swift index 08332920e..2c0a01a69 100644 --- a/Sources/Smithy/URI.swift +++ b/Sources/Smithy/URI.swift @@ -7,7 +7,7 @@ import Foundation /// A representation of the RFC 3986 Uniform Resource Identifier /// Note: URIBuilder returns an URI instance with all components percent encoded -public struct URI: Hashable { +public struct URI: Hashable, Sendable { public let scheme: URIScheme public let path: String public let host: String diff --git a/Sources/Smithy/URIQueryItem.swift b/Sources/Smithy/URIQueryItem.swift index f2ed3d904..dc5c29727 100644 --- a/Sources/Smithy/URIQueryItem.swift +++ b/Sources/Smithy/URIQueryItem.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public struct URIQueryItem: Hashable { +public struct URIQueryItem: Hashable, Sendable { public var name: String public var value: String? diff --git a/Sources/Smithy/URIScheme.swift b/Sources/Smithy/URIScheme.swift index c476a94a7..793f6afea 100644 --- a/Sources/Smithy/URIScheme.swift +++ b/Sources/Smithy/URIScheme.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public enum URIScheme: String, CaseIterable { +public enum URIScheme: String, CaseIterable, Sendable { case http case https diff --git a/Sources/SmithyChecksumsAPI/ChecksumAlgorithm.swift b/Sources/SmithyChecksumsAPI/ChecksumAlgorithm.swift index 1251b2d0e..771ba2883 100644 --- a/Sources/SmithyChecksumsAPI/ChecksumAlgorithm.swift +++ b/Sources/SmithyChecksumsAPI/ChecksumAlgorithm.swift @@ -6,7 +6,7 @@ // /// The list of checksum algorithms contained by this enum may expand in future. -public enum ChecksumAlgorithm { +public enum ChecksumAlgorithm: Sendable { case crc32, crc32c, crc64nvme, sha1, sha256, md5 public func toString() -> String { diff --git a/Sources/SmithyEventStreams/DefaultMessageDecoder.swift b/Sources/SmithyEventStreams/DefaultMessageDecoder.swift index 0777a3420..8a7549a01 100644 --- a/Sources/SmithyEventStreams/DefaultMessageDecoder.swift +++ b/Sources/SmithyEventStreams/DefaultMessageDecoder.swift @@ -16,7 +16,7 @@ import struct Foundation.Data /// AWS implementation of `MessageDecoder` for decoding event stream messages /// Note: This is class because struct does not allow using `self` in closure /// and we need to use `self` to access `messageBuffer` per CRT API. -public class DefaultMessageDecoder: MessageDecoder { +public class DefaultMessageDecoder: MessageDecoder, @unchecked Sendable { private var decoder: EventStreamMessageDecoder? private var messageBuffer: [Message] = [] private var error: Error? diff --git a/Sources/SmithyEventStreams/DefaultMessageDecoderStream.swift b/Sources/SmithyEventStreams/DefaultMessageDecoderStream.swift index e00872098..a95d6b2ca 100644 --- a/Sources/SmithyEventStreams/DefaultMessageDecoderStream.swift +++ b/Sources/SmithyEventStreams/DefaultMessageDecoderStream.swift @@ -11,7 +11,7 @@ import SmithyEventStreamsAuthAPI import struct Foundation.Data /// Stream adapter that decodes input data into `EventStream.Message` objects. -public struct DefaultMessageDecoderStream: MessageDecoderStream { +public struct DefaultMessageDecoderStream: MessageDecoderStream, Sendable { public typealias Element = Event let stream: ReadableStream diff --git a/Sources/SmithyEventStreamsAPI/Header.swift b/Sources/SmithyEventStreamsAPI/Header.swift index ff6e42a03..d57951e1e 100644 --- a/Sources/SmithyEventStreamsAPI/Header.swift +++ b/Sources/SmithyEventStreamsAPI/Header.swift @@ -9,7 +9,7 @@ import Foundation /// A header in an event stream message. /// Headers are used to convey metadata about the message. -public struct Header: Equatable { +public struct Header: Equatable, Sendable { /// The name of the header. public let name: String @@ -25,7 +25,7 @@ public struct Header: Equatable { /// The value of a header in an event stream message. /// Encoders and decoders may use this to determine how to encode or decode the header. -public enum HeaderValue: Equatable { +public enum HeaderValue: Equatable, Sendable { case bool(Bool) case byte(Int8) case int16(Int16) diff --git a/Sources/SmithyEventStreamsAPI/Message.swift b/Sources/SmithyEventStreamsAPI/Message.swift index ddf94b88d..8ad27dfcd 100644 --- a/Sources/SmithyEventStreamsAPI/Message.swift +++ b/Sources/SmithyEventStreamsAPI/Message.swift @@ -8,7 +8,7 @@ import struct Foundation.Data /// A message in an event stream that can be sent or received. -public struct Message: Equatable { +public struct Message: Equatable, Sendable { /// The headers associated with the message. public let headers: [Header] diff --git a/Sources/SmithyEventStreamsAPI/MessageDecoder.swift b/Sources/SmithyEventStreamsAPI/MessageDecoder.swift index 3b06d24f8..d2104df30 100644 --- a/Sources/SmithyEventStreamsAPI/MessageDecoder.swift +++ b/Sources/SmithyEventStreamsAPI/MessageDecoder.swift @@ -8,7 +8,7 @@ import struct Foundation.Data /// Decodes a `Data` object into a `Message` object. -public protocol MessageDecoder { +public protocol MessageDecoder: Sendable { /// Feeds data into the decoder, which may or may not result in a message. /// - Parameter data: The data to feed into the decoder. diff --git a/Sources/SmithyEventStreamsAPI/MessageEncoder.swift b/Sources/SmithyEventStreamsAPI/MessageEncoder.swift index fe122f0bf..b8a4e90d9 100644 --- a/Sources/SmithyEventStreamsAPI/MessageEncoder.swift +++ b/Sources/SmithyEventStreamsAPI/MessageEncoder.swift @@ -9,7 +9,7 @@ import struct Foundation.Data /// Encodes a `Message` into a `Data` object /// to be sent over the wire. -public protocol MessageEncoder { +public protocol MessageEncoder: Sendable { /// Encodes a `Message` into a `Data` object func encode(message: Message) throws -> Data diff --git a/Sources/SmithyEventStreamsAPI/UnmarshalClosure.swift b/Sources/SmithyEventStreamsAPI/UnmarshalClosure.swift index de5c4888c..7ba9a821a 100644 --- a/Sources/SmithyEventStreamsAPI/UnmarshalClosure.swift +++ b/Sources/SmithyEventStreamsAPI/UnmarshalClosure.swift @@ -5,4 +5,4 @@ // SPDX-License-Identifier: Apache-2.0 // -public typealias UnmarshalClosure = (Message) throws -> T +public typealias UnmarshalClosure = @Sendable (Message) throws -> T diff --git a/Sources/SmithyEventStreamsAuthAPI/MessageDataSigner.swift b/Sources/SmithyEventStreamsAuthAPI/MessageDataSigner.swift index ad715581f..b87606d7a 100644 --- a/Sources/SmithyEventStreamsAuthAPI/MessageDataSigner.swift +++ b/Sources/SmithyEventStreamsAuthAPI/MessageDataSigner.swift @@ -10,7 +10,7 @@ import struct SmithyEventStreamsAPI.Message import struct Foundation.Data /// Protocol for signing messages. -public protocol MessageDataSigner { +public protocol MessageDataSigner: Sendable { /// Signs a message. /// - Parameters: @@ -25,7 +25,7 @@ public protocol MessageDataSigner { ) async throws -> SigningResult } -public struct SigningResult { +public struct SigningResult: Sendable { public let output: T public let signature: String diff --git a/Sources/SmithyEventStreamsAuthAPI/MessageDecoderStream.swift b/Sources/SmithyEventStreamsAuthAPI/MessageDecoderStream.swift index 3ddb52c6b..417cf99aa 100644 --- a/Sources/SmithyEventStreamsAuthAPI/MessageDecoderStream.swift +++ b/Sources/SmithyEventStreamsAuthAPI/MessageDecoderStream.swift @@ -7,14 +7,14 @@ /// Stream adapter that decodes input data into `Message` objects. public protocol MessageDecoderStream: AsyncSequence where Event == Element { - associatedtype Event + associatedtype Event: Sendable } -extension MessageDecoderStream { +extension MessageDecoderStream where Self: Sendable { /// Returns an `AsyncThrowingStream` that decodes input data into `Event` objects. public func toAsyncStream() -> AsyncThrowingStream where Event == Element { let stream = AsyncThrowingStream { continuation in - Task { + Task { [self] in // explicitly capture self do { for try await event in self { continuation.yield(event) diff --git a/Sources/SmithyEventStreamsAuthAPI/MessageSigner.swift b/Sources/SmithyEventStreamsAuthAPI/MessageSigner.swift index e3d535263..6d1f6a067 100644 --- a/Sources/SmithyEventStreamsAuthAPI/MessageSigner.swift +++ b/Sources/SmithyEventStreamsAuthAPI/MessageSigner.swift @@ -8,7 +8,7 @@ import SmithyEventStreamsAPI /// Protocol for signing messages. -public protocol MessageSigner { +public protocol MessageSigner: Sendable { /// Signs a message. /// - Parameter message: The message to sign. /// - Returns: The signed message. diff --git a/Sources/SmithyHTTPAPI/Context+HTTP.swift b/Sources/SmithyHTTPAPI/Context+HTTP.swift index 3d40df606..2c5e48934 100644 --- a/Sources/SmithyHTTPAPI/Context+HTTP.swift +++ b/Sources/SmithyHTTPAPI/Context+HTTP.swift @@ -97,7 +97,7 @@ extension ContextBuilder { // @discardableResult attribute we won't get warnings if we // don't end up doing any chaining. @discardableResult - public func with(key: AttributeKey, value: T) -> Self { + public func with(key: AttributeKey, value: T) -> Self { self.attributes.set(key: key, value: value) return self } diff --git a/Sources/SmithyHTTPAPI/Endpoint.swift b/Sources/SmithyHTTPAPI/Endpoint.swift index fd033da18..cce3a0ec4 100644 --- a/Sources/SmithyHTTPAPI/Endpoint.swift +++ b/Sources/SmithyHTTPAPI/Endpoint.swift @@ -7,8 +7,9 @@ import Foundation import Smithy +import enum AwsCommonRuntimeKit.EndpointProperty -public struct Endpoint: Hashable { +public struct Endpoint: Sendable, Equatable { public let uri: URI public let headers: Headers public var protocolType: URIScheme? { uri.scheme } @@ -17,11 +18,11 @@ public struct Endpoint: Hashable { public var host: String { uri.host } public var port: UInt16? { uri.port } public var url: URL? { uri.url } - private let properties: [String: AnyHashable] + private let properties: [String: EndpointPropertyValue] public init(urlString: String, headers: Headers = Headers(), - properties: [String: AnyHashable] = [:]) throws { + properties: [String: EndpointPropertyValue] = [:]) throws { guard let url = URLComponents(string: urlString)?.url else { throw ClientError.unknownError("invalid url \(urlString)") } @@ -31,7 +32,7 @@ public struct Endpoint: Hashable { public init(url: URL, headers: Headers = Headers(), - properties: [String: AnyHashable] = [:]) throws { + properties: [String: EndpointPropertyValue] = [:]) throws { guard let host = url.host else { throw ClientError.unknownError("invalid host \(String(describing: url.host))") @@ -72,24 +73,44 @@ public struct Endpoint: Hashable { public init(uri: URI, headers: Headers = Headers(), - properties: [String: AnyHashable] = [:]) { + properties: [String: EndpointPropertyValue] = [:]) { self.uri = uri self.headers = headers self.properties = properties } } +extension Endpoint { + public init(urlString: String, + headers: Headers = Headers(), + endpointProperties: [String: EndpointProperty]) throws { + guard let url = URLComponents(string: urlString)?.url else { + throw ClientError.unknownError("invalid url \(urlString)") + } + + let properties = endpointProperties.mapValues(EndpointPropertyValue.init) + try self.init(url: url, headers: headers, properties: properties) + } +} + extension Endpoint { /// Returns list of auth schemes /// This is an internal API and subject to change without notice /// - Returns: list of auth schemes if present - public func authSchemes() -> [[String: Any]]? { - guard let schemes = properties[AuthSchemeKeys.authSchemes] as? [[String: Any]] else { + public func authSchemes() -> [[String: EndpointPropertyValue]]? { + guard let value = properties[AuthSchemeKeys.authSchemes], + case let .array(schemeArray) = value else { return nil } - return schemes + return schemeArray.compactMap { scheme in + guard case let .dictionary(dict) = scheme else { + return nil + } + + return dict + } } } diff --git a/Sources/SmithyHTTPAPI/EndpointPropertyValue.swift b/Sources/SmithyHTTPAPI/EndpointPropertyValue.swift new file mode 100644 index 000000000..3d6a9dd31 --- /dev/null +++ b/Sources/SmithyHTTPAPI/EndpointPropertyValue.swift @@ -0,0 +1,62 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Smithy +import enum AwsCommonRuntimeKit.EndpointProperty + +public enum EndpointPropertyValue: Sendable, Equatable { + case bool(Bool) + case string(String) + indirect case array([EndpointPropertyValue]) + indirect case dictionary([String: EndpointPropertyValue]) +} + +public extension EndpointPropertyValue { + // Convenience for string literals + init(_ string: String) { self = .string(string) } + init(_ bool: Bool) { self = .bool(bool) } +} + +extension EndpointPropertyValue { + init(_ endpointProperty: EndpointProperty) { + switch endpointProperty { + case .bool(let value): + self = .bool(value) + case .string(let value): + self = .string(value) + case .array(let values): + self = .array(values.map(EndpointPropertyValue.init)) + case .dictionary(let dict): + self = .dictionary(dict.mapValues(EndpointPropertyValue.init)) + } + } +} + +extension EndpointPropertyValue: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .string(value) + } +} + +extension EndpointPropertyValue: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value) + } +} + +extension EndpointPropertyValue: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: EndpointPropertyValue...) { + self = .array(elements) + } +} + +extension EndpointPropertyValue: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, EndpointPropertyValue)...) { + self = .dictionary(Dictionary(uniqueKeysWithValues: elements)) + } +} diff --git a/Sources/SmithyHTTPAPI/HTTPMethodType.swift b/Sources/SmithyHTTPAPI/HTTPMethodType.swift index 5e8647e7c..0be774515 100644 --- a/Sources/SmithyHTTPAPI/HTTPMethodType.swift +++ b/Sources/SmithyHTTPAPI/HTTPMethodType.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public enum HTTPMethodType: String { +public enum HTTPMethodType: String, Sendable { case connect = "CONNECT" case delete = "DELETE" case get = "GET" diff --git a/Sources/SmithyHTTPAPI/HTTPRequest.swift b/Sources/SmithyHTTPAPI/HTTPRequest.swift index 87077a5f4..224fea91f 100644 --- a/Sources/SmithyHTTPAPI/HTTPRequest.swift +++ b/Sources/SmithyHTTPAPI/HTTPRequest.swift @@ -25,7 +25,8 @@ import struct Foundation.URLRequest // we need to maintain a reference to this same request while we add headers // in the CRT engine so that is why it's a class -public final class HTTPRequest: RequestMessage { +// developer must ensure HTTPRequest’s internal state remains thread-safe +public final class HTTPRequest: RequestMessage, @unchecked Sendable { public var body: ByteStream public let destination: URI public var headers: Headers diff --git a/Sources/SmithyHTTPAuth/AWSSigningConfig.swift b/Sources/SmithyHTTPAuth/AWSSigningConfig.swift index 4a48c6872..4765ca3f7 100644 --- a/Sources/SmithyHTTPAuth/AWSSigningConfig.swift +++ b/Sources/SmithyHTTPAuth/AWSSigningConfig.swift @@ -17,7 +17,7 @@ import struct Foundation.TimeInterval import struct SmithyHTTPAuthAPI.SigningFlags import struct SmithyIdentity.AWSCredentialIdentity -public struct AWSSigningConfig { +public struct AWSSigningConfig: Sendable { public let credentials: AWSCredentialIdentity? public let awsCredentialIdentityResolver: (any AWSCredentialIdentityResolver)? public let expiration: TimeInterval @@ -27,7 +27,7 @@ public struct AWSSigningConfig { public let date: Date public let service: String public let region: String - public let shouldSignHeader: ((String) -> Bool)? + public let shouldSignHeader: (@Sendable (String) -> Bool)? public let signatureType: AWSSignatureType public let signingAlgorithm: SigningAlgorithm @@ -41,7 +41,7 @@ public struct AWSSigningConfig { date: Date, service: String, region: String, - shouldSignHeader: ((String) -> Bool)? = nil, + shouldSignHeader: (@Sendable (String) -> Bool)? = nil, signatureType: AWSSignatureType, signingAlgorithm: SigningAlgorithm ) { diff --git a/Sources/SmithyHTTPAuth/SigV4Signer.swift b/Sources/SmithyHTTPAuth/SigV4Signer.swift index 65cd4a9b5..38fa755ee 100644 --- a/Sources/SmithyHTTPAuth/SigV4Signer.swift +++ b/Sources/SmithyHTTPAuth/SigV4Signer.swift @@ -30,7 +30,7 @@ import struct Foundation.TimeInterval import struct Foundation.URL import SmithyHTTPClient -public class SigV4Signer: SmithyHTTPAuthAPI.Signer { +public class SigV4Signer: SmithyHTTPAuthAPI.Signer, @unchecked Sendable { public init() {} public func signRequest( diff --git a/Sources/SmithyHTTPAuth/Signers/BearerTokenSigner.swift b/Sources/SmithyHTTPAuth/Signers/BearerTokenSigner.swift index fa3a4ab28..d7e9fdaf8 100644 --- a/Sources/SmithyHTTPAuth/Signers/BearerTokenSigner.swift +++ b/Sources/SmithyHTTPAuth/Signers/BearerTokenSigner.swift @@ -14,7 +14,7 @@ import struct SmithyIdentity.BearerTokenIdentity /// The signer for HTTP bearer auth. /// Adds the Authorization header to the request using the resolved bearer token identity as its value. -public class BearerTokenSigner: Signer { +public class BearerTokenSigner: Signer, @unchecked Sendable { public init() {} public func signRequest( diff --git a/Sources/SmithyHTTPAuthAPI/AuthScheme.swift b/Sources/SmithyHTTPAuthAPI/AuthScheme.swift index ab1c54faf..a88e11d20 100644 --- a/Sources/SmithyHTTPAuthAPI/AuthScheme.swift +++ b/Sources/SmithyHTTPAuthAPI/AuthScheme.swift @@ -9,7 +9,7 @@ import class Smithy.Context import protocol SmithyIdentityAPI.IdentityResolver import struct Smithy.Attributes -public protocol AuthScheme { +public protocol AuthScheme: Sendable { var schemeID: String { get } var signer: Signer { get } diff --git a/Sources/SmithyHTTPAuthAPI/AuthSchemeResolver.swift b/Sources/SmithyHTTPAuthAPI/AuthSchemeResolver.swift index fdb78ad47..c65ac856c 100644 --- a/Sources/SmithyHTTPAuthAPI/AuthSchemeResolver.swift +++ b/Sources/SmithyHTTPAuthAPI/AuthSchemeResolver.swift @@ -7,7 +7,7 @@ import class Smithy.Context -public protocol AuthSchemeResolver { +public protocol AuthSchemeResolver: Sendable { func resolveAuthScheme(params: AuthSchemeResolverParameters) throws -> [AuthOption] func constructParameters(context: Context) throws -> AuthSchemeResolverParameters } diff --git a/Sources/SmithyHTTPAuthAPI/SelectedAuthScheme.swift b/Sources/SmithyHTTPAuthAPI/SelectedAuthScheme.swift index a2b9f0b58..33e163b6c 100644 --- a/Sources/SmithyHTTPAuthAPI/SelectedAuthScheme.swift +++ b/Sources/SmithyHTTPAuthAPI/SelectedAuthScheme.swift @@ -9,7 +9,7 @@ import protocol SmithyIdentityAPI.Identity import struct Smithy.Attributes import struct Smithy.AttributeKey -public struct SelectedAuthScheme { +public struct SelectedAuthScheme: Sendable { public let schemeID: String public let identity: Identity? public let signingProperties: Attributes? @@ -24,7 +24,7 @@ public struct SelectedAuthScheme { } extension SelectedAuthScheme { - public func getCopyWithUpdatedSigningProperty(key: AttributeKey, value: T) -> SelectedAuthScheme { + public func getCopyWithUpdatedSigningProperty(key: AttributeKey, value: T) -> SelectedAuthScheme { var updatedSigningProperties = self.signingProperties updatedSigningProperties?.set(key: key, value: value) return SelectedAuthScheme( diff --git a/Sources/SmithyHTTPAuthAPI/Signer.swift b/Sources/SmithyHTTPAuthAPI/Signer.swift index e5527e73a..0675d3765 100644 --- a/Sources/SmithyHTTPAuthAPI/Signer.swift +++ b/Sources/SmithyHTTPAuthAPI/Signer.swift @@ -9,7 +9,7 @@ import class SmithyHTTPAPI.HTTPRequestBuilder import protocol SmithyIdentityAPI.Identity import struct Smithy.Attributes -public protocol Signer { +public protocol Signer: Sendable { func signRequest( requestBuilder: HTTPRequestBuilder, diff --git a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignatureType.swift b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignatureType.swift index 8aabd0d95..960abbfeb 100644 --- a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignatureType.swift +++ b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignatureType.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public enum AWSSignatureType { +public enum AWSSignatureType: Sendable { case requestHeaders case requestQueryParams case requestChunk diff --git a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyHeader.swift b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyHeader.swift index dfb4151b9..131d7e7f7 100644 --- a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyHeader.swift +++ b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyHeader.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public enum AWSSignedBodyHeader { +public enum AWSSignedBodyHeader: Sendable { case none case contentSha256 } diff --git a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift index d4110fca8..6389998ee 100644 --- a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift +++ b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/AWSSignedBodyValue.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public enum AWSSignedBodyValue { +public enum AWSSignedBodyValue: Sendable { case empty case emptySha256 case unsignedPayload diff --git a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningAlgorithm.swift b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningAlgorithm.swift index 098272da3..130879dc2 100644 --- a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningAlgorithm.swift +++ b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningAlgorithm.swift @@ -7,7 +7,7 @@ /// Type of signing algorithm /// String raw value used for serialization and deserialization -public enum SigningAlgorithm: String { +public enum SigningAlgorithm: String, Sendable { /// Signature Version 4 case sigv4 /// Signature Version 4 Asymmetric diff --git a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningFlags.swift b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningFlags.swift index ea1c431d4..59f1e028f 100644 --- a/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningFlags.swift +++ b/Sources/SmithyHTTPAuthAPI/SigningConfigFields/SigningFlags.swift @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -public struct SigningFlags { +public struct SigningFlags: Sendable { public let useDoubleURIEncode: Bool public let shouldNormalizeURIPath: Bool public let omitSessionToken: Bool diff --git a/Sources/SmithyIdentityAPI/FlowType.swift b/Sources/SmithyIdentityAPI/FlowType.swift index fe5a19448..c8943ed0f 100644 --- a/Sources/SmithyIdentityAPI/FlowType.swift +++ b/Sources/SmithyIdentityAPI/FlowType.swift @@ -6,6 +6,6 @@ // /// The type of flow the middleware context is being constructed for -public enum FlowType { +public enum FlowType: Sendable { case NORMAL, PRESIGN_REQUEST, PRESIGN_URL } diff --git a/Sources/SmithyIdentityAPI/Identity.swift b/Sources/SmithyIdentityAPI/Identity.swift index 621634e9f..0144f72a1 100644 --- a/Sources/SmithyIdentityAPI/Identity.swift +++ b/Sources/SmithyIdentityAPI/Identity.swift @@ -8,6 +8,6 @@ import struct Foundation.Date // Base protocol for all identity types -public protocol Identity { +public protocol Identity: Sendable { var expiration: Date? { get } } diff --git a/Sources/SmithyIdentityAPI/IdentityResolver.swift b/Sources/SmithyIdentityAPI/IdentityResolver.swift index 09b5bb3c5..53052f045 100644 --- a/Sources/SmithyIdentityAPI/IdentityResolver.swift +++ b/Sources/SmithyIdentityAPI/IdentityResolver.swift @@ -8,7 +8,7 @@ import struct Smithy.Attributes // Base protocol for all identity provider types -public protocol IdentityResolver { +public protocol IdentityResolver: Sendable { associatedtype IdentityT: Identity func getIdentity(identityProperties: Attributes?) async throws -> IdentityT diff --git a/Sources/SmithyStreams/FileStream.swift b/Sources/SmithyStreams/FileStream.swift index 25908c53c..c9431fadf 100644 --- a/Sources/SmithyStreams/FileStream.swift +++ b/Sources/SmithyStreams/FileStream.swift @@ -140,3 +140,24 @@ public final class FileStream: Stream, @unchecked Sendable { close() } } + +fileprivate extension FileHandle { + func length() throws -> UInt64 { + let length: UInt64 + let savedPos: UInt64 + if #available(macOS 11, tvOS 13.4, iOS 13.4, watchOS 6.2, *) { + savedPos = try offset() + try seekToEnd() + length = try offset() + } else { + savedPos = offsetInFile + seekToEndOfFile() + length = offsetInFile + } + guard length != savedPos else { + return length + } + try self.seek(toOffset: savedPos) + return length + } +} diff --git a/Sources/SmithyStreams/StreamableHttpBody.swift b/Sources/SmithyStreams/StreamableHttpBody.swift index e0479819d..e94f6dc54 100644 --- a/Sources/SmithyStreams/StreamableHttpBody.swift +++ b/Sources/SmithyStreams/StreamableHttpBody.swift @@ -109,4 +109,12 @@ public class StreamableHttpBody: IStreamable { return nil } } + + public func isEndOfStream() -> Bool { + do { + return try self.position >= self.length() + } catch { + return false + } + } } diff --git a/Sources/SmithyTestUtil/ProtocolTestClient.swift b/Sources/SmithyTestUtil/ProtocolTestClient.swift index d55f2402e..c3d591fb8 100644 --- a/Sources/SmithyTestUtil/ProtocolTestClient.swift +++ b/Sources/SmithyTestUtil/ProtocolTestClient.swift @@ -22,7 +22,7 @@ extension ProtocolTestClient: HTTPClient { } } -public class ProtocolTestIdempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator { +public class ProtocolTestIdempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator, @unchecked Sendable { public init() {} public func generateToken() -> String { diff --git a/Sources/SmithyWaitersAPI/Waiter.swift b/Sources/SmithyWaitersAPI/Waiter.swift index c25ee8444..790888591 100644 --- a/Sources/SmithyWaitersAPI/Waiter.swift +++ b/Sources/SmithyWaitersAPI/Waiter.swift @@ -11,7 +11,7 @@ import Foundation /// the desired state will never be reached. /// Intended to be a generic type for use when waiting on any Smithy `operation`. /// May be reused for multiple waits, including concurrent operations. -public class Waiter { +public class Waiter { /// The configuration this waiter was created with. public let config: WaiterConfiguration diff --git a/Sources/SmithyWaitersAPI/WaiterErrors.swift b/Sources/SmithyWaitersAPI/WaiterErrors.swift index 736fdc758..e4a769f68 100644 --- a/Sources/SmithyWaitersAPI/WaiterErrors.swift +++ b/Sources/SmithyWaitersAPI/WaiterErrors.swift @@ -11,7 +11,7 @@ public struct WaiterConfigurationError: Error { } /// Thrown when a waiter matches a failure acceptor on an attempt to call the waited operation. -public struct WaiterFailureError: Error { +public struct WaiterFailureError: Error { /// The number of attempts at the operation that were made while waiting. public let attempts: Int diff --git a/Sources/SmithyWaitersAPI/WaiterRetryer.swift b/Sources/SmithyWaitersAPI/WaiterRetryer.swift index 4f8cdae4f..0ebd625cd 100644 --- a/Sources/SmithyWaitersAPI/WaiterRetryer.swift +++ b/Sources/SmithyWaitersAPI/WaiterRetryer.swift @@ -11,7 +11,7 @@ import Foundation /// Called by the `Waiter` to make the initial request, then called again for an additional request when retry /// is needed. /// A custom closure may be injected at initialization to allow `WaiterRetryer` to be used as a mock. -struct WaiterRetryer { +struct WaiterRetryer { typealias WaitThenRequest = (WaiterScheduler, Input, WaiterConfiguration, (Input) async throws -> Output) async throws -> WaiterOutcome? diff --git a/Tests/ClientRuntimeTests/Endpoints/EndpointsAuthSchemeResolverTests.swift b/Tests/ClientRuntimeTests/Endpoints/EndpointsAuthSchemeResolverTests.swift index f14d82e0a..c7f2edc0b 100644 --- a/Tests/ClientRuntimeTests/Endpoints/EndpointsAuthSchemeResolverTests.swift +++ b/Tests/ClientRuntimeTests/Endpoints/EndpointsAuthSchemeResolverTests.swift @@ -7,6 +7,7 @@ import XCTest @testable import ClientRuntime +import enum SmithyHTTPAPI.EndpointPropertyValue final class EndpointsAuthSchemeResolverTests: XCTestCase { func testParsing() throws { @@ -15,7 +16,7 @@ final class EndpointsAuthSchemeResolverTests: XCTestCase { "name": "sigv4", "signingName": "s3", "disableDoubleEncoding": true - ] as [String: AnyHashable] + ] as [String: EndpointPropertyValue] let actualSigV4 = try EndpointsAuthScheme(from: sigV4) if case let .sigV4(data) = actualSigV4 { @@ -33,7 +34,7 @@ final class EndpointsAuthSchemeResolverTests: XCTestCase { "name": "sigv4a", "signingName": "s3", "disableDoubleEncoding": true - ] as [String: AnyHashable] + ] as [String: EndpointPropertyValue] let actualSigV4a = try EndpointsAuthScheme(from: sigV4A) if case let .sigV4A(data) = actualSigV4a { @@ -70,4 +71,4 @@ final class EndpointsAuthSchemeResolverTests: XCTestCase { XCTAssertThrowsError(try resolver.resolve(authSchemes: [none, sigV4A])) } -} \ No newline at end of file +} diff --git a/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift b/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift index 3d1e48f5d..ee931e4ea 100644 --- a/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift +++ b/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift @@ -20,7 +20,7 @@ class InterceptorTests: XCTestCase { public var property: String? } - struct AddAttributeInterceptor: Interceptor { + struct AddAttributeInterceptor: Interceptor { private let key: AttributeKey private let value: T diff --git a/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift b/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift index a9b3ae478..53deae67c 100644 --- a/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift +++ b/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift @@ -74,7 +74,7 @@ final class SwiftLoggerTests: XCTestCase { } -private class TestLogHandler: LogHandler { +private class TestLogHandler: LogHandler, @unchecked Sendable { let label: String var invocations = [TestLogInvocation]() diff --git a/Tests/ClientRuntimeTests/NetworkingTests/EndpointTests.swift b/Tests/ClientRuntimeTests/NetworkingTests/EndpointTests.swift index 41b3f1a34..fd58365ac 100644 --- a/Tests/ClientRuntimeTests/NetworkingTests/EndpointTests.swift +++ b/Tests/ClientRuntimeTests/NetworkingTests/EndpointTests.swift @@ -28,7 +28,6 @@ class EndpointTests: XCTestCase { let endpoint1 = try Endpoint(url: url) let endpoint2 = try Endpoint(url: url) XCTAssertEqual(endpoint1, endpoint2) - XCTAssertEqual(endpoint1.hashValue, endpoint2.hashValue) } func test_path_percentEncodedInput() throws { diff --git a/Tests/SmithyIdentityTests/MockAWSCredentialIdentityResolver.swift b/Tests/SmithyIdentityTests/MockAWSCredentialIdentityResolver.swift index 451562b1e..bebdfb89c 100644 --- a/Tests/SmithyIdentityTests/MockAWSCredentialIdentityResolver.swift +++ b/Tests/SmithyIdentityTests/MockAWSCredentialIdentityResolver.swift @@ -10,9 +10,9 @@ import struct Smithy.Attributes import struct SmithyIdentity.AWSCredentialIdentity struct MockAWSCredentialIdentityResolver: AWSCredentialIdentityResolver { - let _getIdentity: () async throws -> AWSCredentialIdentity + let _getIdentity: @Sendable () async throws -> AWSCredentialIdentity - init(_ _getIdentity: @escaping () async throws -> AWSCredentialIdentity) { + init(_ _getIdentity: @escaping @Sendable () async throws -> AWSCredentialIdentity) { self._getIdentity = _getIdentity } diff --git a/Tests/SmithyJSONTests/FloatingPointTests.swift b/Tests/SmithyJSONTests/FloatingPointTests.swift index 693cd3830..2389a0e28 100644 --- a/Tests/SmithyJSONTests/FloatingPointTests.swift +++ b/Tests/SmithyJSONTests/FloatingPointTests.swift @@ -15,32 +15,30 @@ import XCTest // This set of tests is intended to verify that Doubles and Floats can be read accurately from JSON. final class FloatingPointTests: XCTestCase { - - static var originalFloats = [Float]() - static var floatData = Data() - static var originalDoubles = [Double]() - static var doubleData = Data() + var originalFloats = [Float]() + var floatData = Data() + var originalDoubles = [Double]() + var doubleData = Data() // Renders 250 floats & doubles to JSON arrays. Numbers are random so they will // likely use all available decimal places. - static override func setUp() { + override func setUp() { + super.setUp() originalFloats = (1..<250).map { _ in Float.random(in: 0.0...1.0) } originalDoubles = (1..<250).map { _ in Double.random(in: 0.0...1.0) } floatData = try! JSONEncoder().encode(originalFloats) doubleData = try! JSONEncoder().encode(originalDoubles) } - // Read the floats from JSON using JSONReader, and compare the values to the original. func test_floatRead() throws { - let reader = try Reader.from(data: Self.floatData) + let reader = try Reader.from(data: floatData) let floats = try reader.readList(memberReadingClosure: ReadingClosures.readFloat(from:), memberNodeInfo: "", isFlattened: false) - XCTAssert(floats == Self.originalFloats) + XCTAssert(floats == originalFloats) } - // Read the doubles from JSON using JSONReader, and compare the values to the original. func test_doubleRead() throws { - let reader = try Reader.from(data: Self.doubleData) + let reader = try Reader.from(data: doubleData) let doubles = try reader.readList(memberReadingClosure: ReadingClosures.readDouble(from:), memberNodeInfo: "", isFlattened: false) - XCTAssert(doubles == Self.originalDoubles) + XCTAssert(doubles == originalDoubles) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EndpointTestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EndpointTestGenerator.kt index 058d23542..398316bf6 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EndpointTestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EndpointTestGenerator.kt @@ -76,7 +76,6 @@ class EndpointTestGenerator( writer, value, if (idx < applicableParams.count() - 1) "," else "", - false, ) } } @@ -99,10 +98,10 @@ class EndpointTestGenerator( testCase.expect.endpoint.ifPresent { endpoint -> writer.write("let actual = try resolver.resolve(params: endpointParams)").write("") - // [String: AnyHashable] can't be constructed from a dictionary literal + // [String: EndpointPropertyValue] can't be constructed from a dictionary literal // first create a string JSON string literal - // then convert to [String: AnyHashable] using JSONSerialization.jsonObject(with:) - writer.openBlock("let properties: [String: AnyHashable] = ", "") { + // then convert to [String: EndpointPropertyValue] using JSONSerialization.jsonObject(with:) + writer.openBlock("let properties: [String: \$N] = ", "", SmithyHTTPAPITypes.EndpointPropertyValue) { generateProperties(writer, endpoint.properties) } @@ -142,7 +141,7 @@ class EndpointTestGenerator( val value = Value.fromNode(second) writer.writeInline("\$S: ", first) writer.call { - generateValue(writer, value, if (idx < properties.values.count() - 1) "," else "", true) + generateValue(writer, value, if (idx < properties.values.count() - 1) "," else "") } } } @@ -156,7 +155,6 @@ class EndpointTestGenerator( writer: SwiftWriter, value: Value, delimeter: String, - castToAnyHashable: Boolean, ) { when (value) { is StringValue -> { @@ -176,11 +174,10 @@ class EndpointTestGenerator( } is ArrayValue -> { - val castStmt = if (castToAnyHashable) " as [AnyHashable]$delimeter" else delimeter - writer.openBlock("[", "]$castStmt") { + writer.openBlock("[", "]$delimeter") { value.values.forEachIndexed { idx, item -> writer.call { - generateValue(writer, item, if (idx < value.values.count() - 1) "," else "", castToAnyHashable) + generateValue(writer, item, if (idx < value.values.count() - 1) "," else "") } } } @@ -190,11 +187,11 @@ class EndpointTestGenerator( if (value.value.isEmpty()) { writer.writeInline("[:]") } else { - writer.openBlock("[", "] as [String: AnyHashable]$delimeter") { + writer.openBlock("[", "]$delimeter") { value.value.map { it.key to it.value }.forEachIndexed { idx, (first, second) -> writer.writeInline("\$S: ", first.name) writer.call { - generateValue(writer, second, if (idx < value.value.count() - 1) "," else "", castToAnyHashable) + generateValue(writer, second, if (idx < value.value.count() - 1) "," else "") } } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt index e1981aed8..631d33338 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt @@ -56,7 +56,7 @@ class SwiftDependency( SwiftDependency( "AwsCommonRuntimeKit", null, - "0.31.0", + "0.52.1", "https://github.com/awslabs/aws-crt-swift", "", "aws-crt-swift", diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt index 03cb1c353..fc69ce3fb 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt @@ -41,7 +41,7 @@ class EndpointParamsGenerator( writer: SwiftWriter, endpointRuleSet: EndpointRuleSet?, ) { - writer.openBlock("public struct EndpointParams {", "}") { + writer.openBlock("public struct EndpointParams: Sendable {", "}") { endpointRuleSet?.parameters?.toList()?.sortedBy { it.name.toString() }?.let { sortedParameters -> renderMembers(writer, sortedParameters) writer.write("") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyHTTPAPITypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyHTTPAPITypes.kt index 8d2788a82..b55648e11 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyHTTPAPITypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyHTTPAPITypes.kt @@ -12,6 +12,7 @@ import software.amazon.smithy.swift.codegen.SwiftDependency */ object SmithyHTTPAPITypes { val Endpoint = runtimeSymbol("Endpoint", SwiftDeclaration.STRUCT) + val EndpointPropertyValue = runtimeSymbol("EndpointPropertyValue", SwiftDeclaration.ENUM) val HttpClient = runtimeSymbol("HTTPClient", SwiftDeclaration.PROTOCOL) val Header = runtimeSymbol("Header", SwiftDeclaration.STRUCT) val Headers = runtimeSymbol("Headers", SwiftDeclaration.STRUCT)