diff --git a/Package.swift b/Package.swift index 10c895f1b..cc116a1ef 100644 --- a/Package.swift +++ b/Package.swift @@ -53,13 +53,16 @@ let package = Package( .library(name: "SmithyCBOR", targets: ["SmithyCBOR"]), .library(name: "SmithyWaitersAPI", targets: ["SmithyWaitersAPI"]), .library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"]), + .library(name: "SmithySwiftNIO", targets: ["SmithySwiftNIO"]), + .library(name: "SmithyTelemetryAPI", targets: ["SmithyTelemetryAPI"]), + .library(name: "SmithyHTTPClientAPI", targets: ["SmithyHTTPClientAPI"]), ], dependencies: { var dependencies: [Package.Dependency] = [ .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.54.2"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.13.0"), - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.26.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", exact: "1.22.0"), ] let isDocCEnabled = ProcessInfo.processInfo.environment["AWS_SWIFT_SDK_ENABLE_DOCC"] != nil @@ -75,10 +78,27 @@ let package = Package( .product(name: "Logging", package: "swift-log"), ] ), + .target( + name: "SmithyTelemetryAPI", + dependencies: [ + "Smithy", + ] + ), + .target( + name: "SmithyHTTPClientAPI", + dependencies: [ + "Smithy", + "SmithyHTTPAPI", + "SmithyTelemetryAPI", + .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"), + ] + ), .target( name: "ClientRuntime", dependencies: [ "Smithy", + "SmithyTelemetryAPI", + "SmithyHTTPClientAPI", "SmithyRetriesAPI", "SmithyRetries", "SmithyXML", @@ -98,7 +118,6 @@ let package = Package( "SmithyChecksums", "SmithyCBOR", .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"), - .product(name: "AsyncHTTPClient", package: "async-http-client"), // Only include these on macOS, iOS, tvOS, watchOS, and macCatalyst (visionOS and Linux are excluded) .product( name: "InMemoryExporter", @@ -125,6 +144,17 @@ let package = Package( .copy("PrivacyInfo.xcprivacy") ] ), + .target( + name: "SmithySwiftNIO", + dependencies: [ + "Smithy", + "SmithyHTTPAPI", + "SmithyStreams", + "SmithyHTTPClientAPI", + .product(name: "AsyncHTTPClient", package: "async-http-client"), + ], + path: "Sources/SmithySwiftNIO" + ), .target( name: "SmithyRetriesAPI" ), @@ -270,6 +300,13 @@ let package = Package( ], resources: [ .process("Resources") ] ), + .testTarget( + name: "SmithySwiftNIOTests", + dependencies: [ + "SmithySwiftNIO", + "SmithyTestUtil", + ] + ), .testTarget( name: "SmithyCBORTests", dependencies: ["SmithyCBOR", "ClientRuntime", "SmithyTestUtil"] diff --git a/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift b/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift index 21e48406f..4868cc7ee 100644 --- a/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift @@ -13,6 +13,7 @@ import protocol SmithyHTTPAuthAPI.AuthSchemeResolver import protocol SmithyHTTPAuthAPI.AuthSchemeResolverParameters import struct SmithyRetries.DefaultRetryStrategy import struct SmithyRetries.ExponentialBackoffStrategy +import SmithyTelemetryAPI import protocol SmithyRetriesAPI.RetryErrorInfoProvider import protocol SmithyRetriesAPI.RetryStrategy import struct SmithyRetriesAPI.RetryStrategyOptions diff --git a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift index 4eab0bf56..1b61a9467 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift @@ -19,7 +19,10 @@ import protocol SmithyHTTPAPI.HTTPClient import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse import enum SmithyHTTPAPI.HTTPStatusCode +import class SmithyHTTPClientAPI.HttpTelemetry +import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys import class SmithyStreams.BufferedStream +import SmithyTelemetryAPI #if os(Linux) import Glibc #elseif !os(Windows) diff --git a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift index 25b13d4b8..1fe696fbb 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngineConfig.swift @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0. */ +import class SmithyHTTPClientAPI.HttpTelemetry + struct CRTClientEngineConfig: Sendable { /// Max connections the manager can contain per endpoint diff --git a/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift b/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift index 6e3192c82..7d871a63e 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift @@ -8,6 +8,8 @@ import AwsCommonRuntimeKit import struct Smithy.Attributes import enum Smithy.ByteStream +import class SmithyHTTPClientAPI.HttpTelemetry +import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys extension HTTP2Stream { /// Returns the recommended size, in bytes, for the data to write diff --git a/Sources/ClientRuntime/Networking/Http/HttpClientConfiguration.swift b/Sources/ClientRuntime/Networking/Http/HttpClientConfiguration.swift index 1f09b439a..b6c5e7bfc 100644 --- a/Sources/ClientRuntime/Networking/Http/HttpClientConfiguration.swift +++ b/Sources/ClientRuntime/Networking/Http/HttpClientConfiguration.swift @@ -5,87 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import AwsCommonRuntimeKit -import struct Foundation.TimeInterval -import enum Smithy.URIScheme -import struct SmithyHTTPAPI.Headers +import SmithyHTTPClientAPI -public class HttpClientConfiguration { - - /// The timeout for establishing a connection, in seconds. - /// - /// If none is provided, the client will use default values based on the platform. - public var connectTimeout: TimeInterval? - - /// The timeout for socket, in seconds. - /// Sets maximum time to wait between two data packets. - /// Used to close stale connections that have no activity. - /// - /// Defaults to 60 seconds if no value is provided. - public var socketTimeout: TimeInterval - - /// HTTP headers to be submitted with every HTTP request. - /// - /// If none is provided, defaults to no extra headers. - public var defaultHeaders: Headers - - /// The maximum connections the HTTP client makes per host or per endpoint depending on the OS. - /// - /// For Apple platforms, it will be per host. - /// For Linux, it will be per endpoint (protocol + host + port). - public var maxConnections: Int - - // add any other properties here you want to give the service operations - // control over to be mapped to the Http Client - - /// The URL scheme to be used for HTTP requests. Supported values are `http` and `https`. - /// - /// If none is provided, the default protocol for the operation will be used - public var protocolType: URIScheme? - - /// Custom TLS configuration for HTTPS connections. - /// - /// Enables specifying client certificates and trust stores for secure communication. - /// Defaults to system's TLS settings if `nil`. - public var tlsConfiguration: (any TLSConfiguration)? - - /// HTTP Client Telemetry - public var telemetry: HttpTelemetry? - - /// Creates a configuration object for a SDK HTTP client. - /// - /// Not all configuration settings may be followed by all clients. - /// - Parameters: - /// - connectTimeout: The maximum time to wait for a connection to be established. - /// - socketTimeout: The maximum time to wait between data packets. - /// - defaultHeaders: HTTP headers to be included with every HTTP request. - /// Note that certain headers may cause your API request to fail. Defaults to no headers. - /// - protocolType: The HTTP scheme (`http` or `https`) to be used for API requests. Defaults to the operation's standard configuration. - /// - tlsConfiguration: Optional custom TLS configuration for HTTPS requests. If `nil`, defaults to a standard configuration. - /// - maxConnections: The maximum number of connections the HTTP client makes per host (for Apple platforms) or per endpoint (for Linux). For non-mac Apple platforms, defaults to 6. For macOS and Linux, defaults to 50. - public init( - connectTimeout: TimeInterval? = nil, - socketTimeout: TimeInterval = 60.0, - protocolType: URIScheme = .https, - defaultHeaders: Headers = Headers(), - tlsConfiguration: (any TLSConfiguration)? = nil, - telemetry: HttpTelemetry? = nil, - maxConnections: Int? = nil - ) { - self.socketTimeout = socketTimeout - self.protocolType = protocolType - self.defaultHeaders = defaultHeaders - self.connectTimeout = connectTimeout - self.tlsConfiguration = tlsConfiguration - self.telemetry = telemetry - if let maxConnections { - self.maxConnections = maxConnections - } else { - #if os(macOS) || os(Linux) - self.maxConnections = 50 - #else // iOS, ipadOS, watchOS, tvOS. - self.maxConnections = 6 // URLSession default. - #endif - } - } -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyHTTPClientAPI. +public typealias HttpClientConfiguration = SmithyHTTPClientAPI.HTTPClientConfiguration diff --git a/Sources/ClientRuntime/Networking/Http/TLSConfiguration.swift b/Sources/ClientRuntime/Networking/Http/TLSConfiguration.swift new file mode 100644 index 000000000..9cac5294f --- /dev/null +++ b/Sources/ClientRuntime/Networking/Http/TLSConfiguration.swift @@ -0,0 +1,12 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import SmithyHTTPClientAPI + +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyHTTPClientAPI. +public typealias TLSConfiguration = SmithyHTTPClientAPI.TLSConfiguration diff --git a/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift b/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift index bbfacedd1..a5c420e18 100644 --- a/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift +++ b/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift @@ -13,6 +13,8 @@ import class Foundation.DispatchQueue import class Foundation.InputStream import class Foundation.NSObject import class Foundation.OutputStream +import class SmithyHTTPClientAPI.HttpTelemetry +import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys import class Foundation.RunLoop import class Foundation.Stream import protocol Foundation.StreamDelegate diff --git a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift index 9a7b475ae..2a0c6ea20 100644 --- a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift +++ b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift @@ -18,6 +18,8 @@ import class Foundation.NSRecursiveLock import var Foundation.NSURLAuthenticationMethodClientCertificate import var Foundation.NSURLAuthenticationMethodServerTrust import struct Foundation.TimeInterval +import class SmithyHTTPClientAPI.HttpTelemetry +import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys import class Foundation.URLAuthenticationChallenge import struct Foundation.URLComponents import class Foundation.URLCredential @@ -27,6 +29,7 @@ import class Foundation.URLResponse import class Foundation.URLSession import class Foundation.URLSessionConfiguration import protocol Foundation.URLSessionDataDelegate +import SmithyTelemetryAPI import class Foundation.URLSessionDataTask import class Foundation.URLSessionTask import class Foundation.URLSessionTaskMetrics diff --git a/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift b/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift index b9bd14066..6f1636f67 100644 --- a/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift +++ b/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // +import SmithyTelemetryAPI + public class TelemetryPlugin: Plugin { private let telemetryProvider: TelemetryProvider diff --git a/Sources/ClientRuntime/Telemetry/Context/TelemetryContext.swift b/Sources/ClientRuntime/Telemetry/Context/TelemetryContext.swift index de1ac42d1..5ca2b50ff 100644 --- a/Sources/ClientRuntime/Telemetry/Context/TelemetryContext.swift +++ b/Sources/ClientRuntime/Telemetry/Context/TelemetryContext.swift @@ -5,14 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// A Telemetry Context is a container that can be associated with Tracers and Metrics. -/// -/// Context implementations may be containers for execution-scoped values across API boundaries (both in-process and -/// distributed). -public protocol TelemetryContext: Sendable { +import SmithyTelemetryAPI - /// Make this context the currently active context. - /// - /// - Returns: the scope of the current context - func makeCurrent() -> TelemetryScope -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TelemetryContext = SmithyTelemetryAPI.TelemetryContext diff --git a/Sources/ClientRuntime/Telemetry/Context/TelemetryContextManager.swift b/Sources/ClientRuntime/Telemetry/Context/TelemetryContextManager.swift index 2954d78df..274e5c1cc 100644 --- a/Sources/ClientRuntime/Telemetry/Context/TelemetryContextManager.swift +++ b/Sources/ClientRuntime/Telemetry/Context/TelemetryContextManager.swift @@ -5,11 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// A Telemetry Context Manager manages the Telemetry Contexts in a client. -/// -/// Implementations should be able to manage contexts in a thread-safe way. -public protocol TelemetryContextManager: Sendable { +import SmithyTelemetryAPI - /// - Returns: the current Telemetry Context - func current() -> TelemetryContext -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TelemetryContextManager = SmithyTelemetryAPI.TelemetryContextManager diff --git a/Sources/ClientRuntime/Telemetry/Context/TelemetryScope.swift b/Sources/ClientRuntime/Telemetry/Context/TelemetryScope.swift index a0fa77c3f..c21fec8da 100644 --- a/Sources/ClientRuntime/Telemetry/Context/TelemetryScope.swift +++ b/Sources/ClientRuntime/Telemetry/Context/TelemetryScope.swift @@ -5,9 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// Delineates a Telemetry Scope that has a beginning and end, particularly for Telemetry Contexts. -public protocol TelemetryScope: Sendable { +import SmithyTelemetryAPI - /// Ends the scope. - func end() -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TelemetryScope = SmithyTelemetryAPI.TelemetryScope diff --git a/Sources/ClientRuntime/Telemetry/DefaultTelemetry.swift b/Sources/ClientRuntime/Telemetry/DefaultTelemetry.swift index 7afe5570a..0eea83b8a 100644 --- a/Sources/ClientRuntime/Telemetry/DefaultTelemetry.swift +++ b/Sources/ClientRuntime/Telemetry/DefaultTelemetry.swift @@ -5,166 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.AttributeKey -import struct Smithy.Attributes -import protocol Smithy.LogAgent -import struct Smithy.SwiftLogger +import SmithyTelemetryAPI -/// Namespace for the Default SDK Telemetry implementations. -public enum DefaultTelemetry: Sendable { - /// The Default SDK Telemetry Provider Implementation. - /// - /// - contextManager: no-op - /// - loggerProvider: provides SwiftLoggers - /// - meterProvider: no-op - /// - tracerProvider: no-op - public static let provider: TelemetryProvider = _DefaultTelemetryProvider() - - fileprivate final class _DefaultTelemetryProvider: TelemetryProvider { - let contextManager: TelemetryContextManager = defaultContextManager - let loggerProvider: LoggerProvider = defaultLoggerProvider - let meterProvider: MeterProvider = defaultMeterProvider - let tracerProvider: TracerProvider = defaultTracerProvider - } -} - -// Context -extension DefaultTelemetry { - public static let defaultContextManager: TelemetryContextManager = NoOpTelemetryContextManager() - fileprivate static let defaultTelemetryContext: TelemetryContext = NoOpTelemetryContext() - fileprivate static let defaultTelemetryScope: TelemetryScope = NoOpTelemetryScope() - - fileprivate final class NoOpTelemetryContextManager: TelemetryContextManager { - func current() -> TelemetryContext { defaultTelemetryContext } - } - - fileprivate final class NoOpTelemetryContext: TelemetryContext { - func makeCurrent() -> TelemetryScope { defaultTelemetryScope } - } - - fileprivate final class NoOpTelemetryScope: TelemetryScope { - func end() {} - } -} - -// Logging -extension DefaultTelemetry { - public static let defaultLoggerProvider: LoggerProvider = _DefaultLoggerProvider() - - fileprivate final class _DefaultLoggerProvider: LoggerProvider { - func getLogger(name: String) -> LogAgent { SwiftLogger(label: name) } - } -} - -// Metrics -extension DefaultTelemetry { - public static let defaultMeterProvider: MeterProvider = NoOpMeterProvider() - fileprivate static let defaultMeter: Meter = NoOpMeter() - fileprivate static let defaultAsyncMeasurementHandle: AsyncMeasurementHandle = NoOpAsyncMeasurementHandle() - fileprivate static let defaultUpDownCounter: UpDownCounter = NoOpUpDownCounter() - fileprivate static let defaultMonotonicCounter: MonotonicCounter = NoOpMonotonicCounter() - fileprivate static let defaultHistogram: Histogram = NoOpHistogram() - - fileprivate final class NoOpMeterProvider: MeterProvider { - func getMeter(scope: String, attributes: Attributes?) -> Meter { defaultMeter } - } - - fileprivate final class NoOpMeter: Meter { - func createGauge( - name: String, - callback: @escaping (any DoubleAsyncMeasurement) -> Void, - units: String?, - description: String? - ) -> AsyncMeasurementHandle { - defaultAsyncMeasurementHandle - } - - func createUpDownCounter( - name: String, - units: String?, - description: String? - ) -> UpDownCounter { - defaultUpDownCounter - } - - func createAsyncUpDownCounter( - name: String, - callback: @escaping (any LongAsyncMeasurement) -> Void, - units: String?, - description: String? - ) -> AsyncMeasurementHandle { - defaultAsyncMeasurementHandle - } - - func createCounter( - name: String, - units: String?, - description: String? - ) -> MonotonicCounter { - defaultMonotonicCounter - } - - func createAsyncMonotonicCounter( - name: String, - callback: @escaping (any LongAsyncMeasurement) -> Void, - units: String?, - description: String? - ) -> AsyncMeasurementHandle { - defaultAsyncMeasurementHandle - } - - func createHistogram( - name: String, - units: String?, - description: String? - ) -> Histogram { - defaultHistogram - } - } - - fileprivate final class NoOpAsyncMeasurementHandle: AsyncMeasurementHandle { - func stop() {} - } - - fileprivate final class NoOpUpDownCounter: UpDownCounter { - func add(value: Int, attributes: Attributes?, context: TelemetryContext?) {} - } - - fileprivate final class NoOpMonotonicCounter: MonotonicCounter { - func add(value: Int, attributes: Attributes?, context: TelemetryContext?) {} - } - - fileprivate final class NoOpHistogram: Histogram { - func record(value: Double, attributes: Attributes?, context: TelemetryContext?) {} - } -} - -// Trace -extension DefaultTelemetry { - public static let defaultTracerProvider: TracerProvider = NoOpTracerProvider() - fileprivate static let defaultTracer: Tracer = NoOpTracer() - fileprivate static let defaultTraceSpan: TraceSpan = NoOpTraceSpan() - - fileprivate final class NoOpTracerProvider: TracerProvider { - func getTracer(scope: String) -> Tracer { defaultTracer } - } - - fileprivate final class NoOpTracer: Tracer { - func createSpan( - name: String, - initialAttributes: Attributes?, - spanKind: SpanKind, - parentContext: TelemetryContext? - ) -> TraceSpan { - defaultTraceSpan - } - } - - fileprivate final class NoOpTraceSpan: TraceSpan { - let name: String = "" - func emitEvent(name: String, attributes: Attributes?) {} - func setAttribute(key: AttributeKey, value: T) {} - func setStatus(status: TraceSpanStatus) {} - func end() {} - } -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias DefaultTelemetry = SmithyTelemetryAPI.DefaultTelemetry diff --git a/Sources/ClientRuntime/Telemetry/Logging/LoggerProvider.swift b/Sources/ClientRuntime/Telemetry/Logging/LoggerProvider.swift index 4a2627dca..cf191f3a5 100644 --- a/Sources/ClientRuntime/Telemetry/Logging/LoggerProvider.swift +++ b/Sources/ClientRuntime/Telemetry/Logging/LoggerProvider.swift @@ -5,13 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import protocol Smithy.LogAgent +import SmithyTelemetryAPI -/// A Logger Provider provides implementations of LogAgents. -public protocol LoggerProvider: Sendable { - /// Provides a LogAgent. - /// - /// - Parameter name: the name associated with the LogAgent - /// - Returns: a LogAgent - func getLogger(name: String) -> LogAgent -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias LoggerProvider = SmithyTelemetryAPI.LoggerProvider diff --git a/Sources/ClientRuntime/Telemetry/Metrics/AsyncMeasurement.swift b/Sources/ClientRuntime/Telemetry/Metrics/AsyncMeasurement.swift index 25e9faa3f..3f02e7230 100644 --- a/Sources/ClientRuntime/Telemetry/Metrics/AsyncMeasurement.swift +++ b/Sources/ClientRuntime/Telemetry/Metrics/AsyncMeasurement.swift @@ -5,29 +5,20 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// Handle to stop recording values of an AsyncMeasurement. -public protocol AsyncMeasurementHandle: Sendable { +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias AsyncMeasurementHandle = SmithyTelemetryAPI.AsyncMeasurementHandle - /// Stop recording values of an AsyncMeasurement. - /// - /// Implementations probably will unregister an AsyncMeasurement callback. - func stop() -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias LongAsyncMeasurement = SmithyTelemetryAPI.LongAsyncMeasurement -public typealias LongAsyncMeasurement = AsyncMeasurement +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias DoubleAsyncMeasurement = SmithyTelemetryAPI.DoubleAsyncMeasurement -public typealias DoubleAsyncMeasurement = AsyncMeasurement - -/// Async measurement of a specific numeric type. -public protocol AsyncMeasurement { - associatedtype NumericType: Numeric - - /// Asynchronously records a value for a metric, usually as a callback to an async instrument created by a Meter. - /// - /// - Parameter value: value to record - /// - Parameter attributes: associated attributes, typically of the metric - /// - Parameter context: context in which value is recorded in - func record(value: NumericType, attributes: Attributes?, context: TelemetryContext?) -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias AsyncMeasurement = SmithyTelemetryAPI.AsyncMeasurement diff --git a/Sources/ClientRuntime/Telemetry/Metrics/Histogram.swift b/Sources/ClientRuntime/Telemetry/Metrics/Histogram.swift index 9aeb869d1..3feaa21b0 100644 --- a/Sources/ClientRuntime/Telemetry/Metrics/Histogram.swift +++ b/Sources/ClientRuntime/Telemetry/Metrics/Histogram.swift @@ -5,17 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// A Histogram measures a value where the statistics are likely meaningful. -/// -/// Examples include: request latency, HTTP response times -public protocol Histogram: Sendable { - - /// Records a value for a metric. - /// - /// - Parameter value: value to record - /// - Parameter attributes: associated attributes, typically of the metric - /// - Parameter context: context in which value is recorded in - func record(value: Double, attributes: Attributes?, context: TelemetryContext?) -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias Histogram = SmithyTelemetryAPI.Histogram diff --git a/Sources/ClientRuntime/Telemetry/Metrics/Meter.swift b/Sources/ClientRuntime/Telemetry/Metrics/Meter.swift index 879dcddbf..0f4611f1a 100644 --- a/Sources/ClientRuntime/Telemetry/Metrics/Meter.swift +++ b/Sources/ClientRuntime/Telemetry/Metrics/Meter.swift @@ -5,107 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// A Meter is the entry point to create the instruments. -/// -/// The following instrument creation entry points are defined: -/// -/// - Gauge -/// - UpDownCounter -/// - Asynchronous UpDownCounter -/// - MonotonicCounter -/// - Asynchronous MonotonicCounter -/// - Histogram -public protocol Meter: Sendable { +import SmithyTelemetryAPI - /// Creates a Gauge, used to measure the current instantaneous value of something. - /// - /// Examples include: the current memory used by a process - /// - /// - Parameter name: the instrument name - /// - Parameter callback: callback invoked when a Gauge value is read - /// - Parameter units: the unit of measure - /// - Parameter description: a description of the metric - /// - Returns: handle to stop recording Gauge metrics - func createGauge( - name: String, - callback: @escaping (any DoubleAsyncMeasurement) -> Void, - units: String?, - description: String? - ) -> AsyncMeasurementHandle - - /// Creates an UpDownCounter, used to measure a value that goes up or down. - /// - /// Examples include: queue length - /// - /// - Parameter name: the instrument name - /// - Parameter units: the unit of measure - /// - Parameter description: a description of the metric - /// - Returns: an UpDownCounter - func createUpDownCounter( - name: String, - units: String?, - description: String? - ) -> UpDownCounter - - /// Creates an asynchronous UpDownCounter, used to measure a value that goes up or down. - /// - /// Use an asynchronous UpDownCounter instead of a synchronous UpDownCounter when only absolute values are - /// available, or it would otherwise be expensive to keep track of continuously. - /// - /// - Parameter name: the instrument name - /// - Parameter callback: callback invoked when an UpDownCounter value is recorded - /// - Parameter units: the unit of measure - /// - Parameter description: a description of the metric - /// - Returns: handle to stop recording UpDownCounter metrics - func createAsyncUpDownCounter( - name: String, - callback: @escaping (any LongAsyncMeasurement) -> Void, - units: String?, - description: String? - ) -> AsyncMeasurementHandle - - /// Creates a MonotonicCounter, used to measure a value that only ever increases. - /// - /// Examples include: total requests received - /// - /// - Parameter name: the instrument name - /// - Parameter units: the unit of measure - /// - Parameter description: a description of the metric - /// - Returns: a MonotonicCounter - func createCounter( - name: String, - units: String?, - description: String? - ) -> MonotonicCounter - - /// Creates an asynchronous MonotonicCounter, used to measure a value that only ever increases. - /// - /// Use an asynchronous MonotonicCounter instead of a synchronous MonotonicCounter when only absolute values are - /// available, or it would otherwise be expensive to keep track of continuously. - /// - /// - Parameter name: the instrument name - /// - Parameter callback: callback invoked when an MonotonicCounter value is recorded - /// - Parameter units: the unit of measure - /// - Parameter description: a description of the metric - /// - Returns: handle to stop recording MonotonicCounter metrics - func createAsyncMonotonicCounter( - name: String, - callback: @escaping (any LongAsyncMeasurement) -> Void, - units: String?, - description: String? - ) -> AsyncMeasurementHandle - - /// Creates a Histogram, used to measure a value where the statistics are likely meaningful. - /// - /// Examples include: request latency, HTTP response times - /// - /// - Parameter name: the instrument name - /// - Parameter units: the unit of measure - /// - Parameter description: a description of the metric - /// - Returns: a Histogram - func createHistogram( - name: String, - units: String?, - description: String? - ) -> Histogram -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias Meter = SmithyTelemetryAPI.Meter diff --git a/Sources/ClientRuntime/Telemetry/Metrics/MeterProvider.swift b/Sources/ClientRuntime/Telemetry/Metrics/MeterProvider.swift index 39f327138..e1859693f 100644 --- a/Sources/ClientRuntime/Telemetry/Metrics/MeterProvider.swift +++ b/Sources/ClientRuntime/Telemetry/Metrics/MeterProvider.swift @@ -5,15 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// A Meter Provider provides implementations of Meters. -public protocol MeterProvider: Sendable { - - /// Provides a Meter. - /// - /// - Parameter scope: the name of the instrumentation scope that uniquely identifies this meter - /// - Parameter attributes: instrumentation scope attributes to associate with emitted telemetry data - /// - Returns: a Meter - func getMeter(scope: String, attributes: Attributes?) -> Meter -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias MeterProvider = SmithyTelemetryAPI.MeterProvider diff --git a/Sources/ClientRuntime/Telemetry/Metrics/MonotonicCounter.swift b/Sources/ClientRuntime/Telemetry/Metrics/MonotonicCounter.swift index 684aa1693..f6b01af8a 100644 --- a/Sources/ClientRuntime/Telemetry/Metrics/MonotonicCounter.swift +++ b/Sources/ClientRuntime/Telemetry/Metrics/MonotonicCounter.swift @@ -5,17 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// A Monotonic Counter measures a value that only ever increases. -/// -/// Examples include: total requests received -public protocol MonotonicCounter: Sendable { - - /// Records a value for a metric. - /// - /// - Parameter value: value to record - /// - Parameter attributes: associated attributes, typically of the metric - /// - Parameter context: context in which value is recorded in - func add(value: Int, attributes: Attributes?, context: TelemetryContext?) -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias MonotonicCounter = SmithyTelemetryAPI.MonotonicCounter diff --git a/Sources/ClientRuntime/Telemetry/Metrics/UpDownCounter.swift b/Sources/ClientRuntime/Telemetry/Metrics/UpDownCounter.swift index 7bc17d005..96d76f5d4 100644 --- a/Sources/ClientRuntime/Telemetry/Metrics/UpDownCounter.swift +++ b/Sources/ClientRuntime/Telemetry/Metrics/UpDownCounter.swift @@ -5,17 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// An UpDownCounter measures a value that goes up or down. -/// -/// Examples include: queue length -public protocol UpDownCounter: Sendable { - - /// Records a value for a metric. - /// - /// - Parameter value: value to record - /// - Parameter attributes: associated attributes, typically of the metric - /// - Parameter context: context in which value is recorded in - func add(value: Int, attributes: Attributes?, context: TelemetryContext?) -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias UpDownCounter = SmithyTelemetryAPI.UpDownCounter diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift index d8e01cdc9..e930728f2 100644 --- a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift @@ -7,6 +7,7 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Foundation +import SmithyTelemetryAPI // OpenTelemetrySdk specific imports @preconcurrency import protocol OpenTelemetrySdk.SpanExporter diff --git a/Sources/ClientRuntime/Telemetry/TelemetryProvider.swift b/Sources/ClientRuntime/Telemetry/TelemetryProvider.swift index 4e4d5d867..38e7a3bc7 100644 --- a/Sources/ClientRuntime/Telemetry/TelemetryProvider.swift +++ b/Sources/ClientRuntime/Telemetry/TelemetryProvider.swift @@ -5,14 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// A Telemetry Provider provides the SDK-level Telemetry configuration values. -public protocol TelemetryProvider: Sendable { - /// The configured Telemetry Context Manager. - var contextManager: any TelemetryContextManager { get } - /// The configured Logger Provider. - var loggerProvider: any LoggerProvider { get } - /// The configured Meter Provider. - var meterProvider: any MeterProvider { get } - /// The configured Tracer Provider. - var tracerProvider: any TracerProvider { get } -} +import SmithyTelemetryAPI + +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TelemetryProvider = SmithyTelemetryAPI.TelemetryProvider diff --git a/Sources/ClientRuntime/Telemetry/Tracing/SpanKind.swift b/Sources/ClientRuntime/Telemetry/Tracing/SpanKind.swift index 4342070bc..07a462f1c 100644 --- a/Sources/ClientRuntime/Telemetry/Tracing/SpanKind.swift +++ b/Sources/ClientRuntime/Telemetry/Tracing/SpanKind.swift @@ -5,19 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// The Kind of Trace Span, which indicates whether a span is a remote child or parent, and whether it is asynchronous. -public enum SpanKind { - /// An `internal` span is the default kind, which represents an internal operation within a client. - case `internal` - /// A `client` span represents a request to a remote service. This span is often the parent of a remote `server` - /// span and does not end until a response is received. - case client - /// A `server` span represents handling a synchronous network request. This span is often the child of a remote - /// `client` span that is expected to wait for the response. - case server - /// A `producer` span represents an asynchronous request. This span is a parent to a corresponding child `consumer` - /// span and may start and/or end before the child consumer span does. - case producer - /// A `consumer` span repesents a child of an asynchronous `producer` request. - case consumer -} +import SmithyTelemetryAPI + +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias SpanKind = SmithyTelemetryAPI.SpanKind diff --git a/Sources/ClientRuntime/Telemetry/Tracing/TraceSpan.swift b/Sources/ClientRuntime/Telemetry/Tracing/TraceSpan.swift index 4bd55bb2c..949371c1d 100644 --- a/Sources/ClientRuntime/Telemetry/Tracing/TraceSpan.swift +++ b/Sources/ClientRuntime/Telemetry/Tracing/TraceSpan.swift @@ -5,34 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.AttributeKey -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// A Trace Span represents a single unit of work which has a beginning and end time, and may be connected to other -/// spans as part of a parent / child relationship. -/// -/// A span with no parent is considered a root span. -public protocol TraceSpan: TelemetryScope { - - /// The name of the span. - var name: String { get } - - /// Emits an event to the span. - /// - /// - Parameter name: event name - /// - Parameter attributes: event attributes - func emitEvent(name: String, attributes: Attributes?) - - /// Set the value for the given attribute key. - /// - /// Implementations will want to restrict the value type to the allowed types. - /// - /// - Parameter key: attribute key - /// - Parameter value: attribute value - func setAttribute(key: AttributeKey, value: T) - - /// Sets the span status. - /// - /// - Parameter status: span status to set - func setStatus(status: TraceSpanStatus) -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TraceSpan = SmithyTelemetryAPI.TraceSpan diff --git a/Sources/ClientRuntime/Telemetry/Tracing/TraceSpanStatus.swift b/Sources/ClientRuntime/Telemetry/Tracing/TraceSpanStatus.swift index a3fd46fd0..9c58887f5 100644 --- a/Sources/ClientRuntime/Telemetry/Tracing/TraceSpanStatus.swift +++ b/Sources/ClientRuntime/Telemetry/Tracing/TraceSpanStatus.swift @@ -5,12 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -/// Status of a Trace Span. -public enum TraceSpanStatus { - /// The `unset` status is the default / implicit status. - case unset - /// The `ok` status indicates the operation is successful. - case ok - /// The `error` status indicates the operation is unsuccessful. - case error -} +import SmithyTelemetryAPI + +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TraceSpanStatus = SmithyTelemetryAPI.TraceSpanStatus diff --git a/Sources/ClientRuntime/Telemetry/Tracing/Tracer.swift b/Sources/ClientRuntime/Telemetry/Tracing/Tracer.swift index 9752846b3..14c36d8c9 100644 --- a/Sources/ClientRuntime/Telemetry/Tracing/Tracer.swift +++ b/Sources/ClientRuntime/Telemetry/Tracing/Tracer.swift @@ -5,25 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// A Tracer is the entry point for creating spans. -/// -/// Implementations MAY provide convenient builder APIs or other extensions to create spans, but ultimately new spans -/// should be created using the tracer interface. -public protocol Tracer: Sendable { - - /// Create a new Trace Span. - /// - /// - Parameter name: name of the span - /// - Parameter initialAttributes: initial span attributes - /// - Parameter spanKind: kind of span - /// - Parameter parentContext: parent context of the span - /// - Returns: returns the new Trace Span - func createSpan( - name: String, - initialAttributes: Attributes?, - spanKind: SpanKind, - parentContext: TelemetryContext? - ) -> TraceSpan -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias Tracer = SmithyTelemetryAPI.Tracer diff --git a/Sources/ClientRuntime/Telemetry/Tracing/TracerProvider.swift b/Sources/ClientRuntime/Telemetry/Tracing/TracerProvider.swift index d9cdf7110..ce762ce93 100644 --- a/Sources/ClientRuntime/Telemetry/Tracing/TracerProvider.swift +++ b/Sources/ClientRuntime/Telemetry/Tracing/TracerProvider.swift @@ -5,14 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -import struct Smithy.Attributes +import SmithyTelemetryAPI -/// A Tracer Provider provides implementations of Tracers. -public protocol TracerProvider: Sendable { - - /// Gets a scoped Tracer. - /// - /// - Parameter scope: the unique scope of the Tracer - /// - Returns: a Tracer - func getTracer(scope: String) -> Tracer -} +/// Typealias for backward compatibility. +/// The actual implementation is now in SmithyTelemetryAPI. +public typealias TracerProvider = SmithyTelemetryAPI.TracerProvider diff --git a/Sources/SmithyHTTPClientAPI/HttpClientConfiguration.swift b/Sources/SmithyHTTPClientAPI/HttpClientConfiguration.swift new file mode 100644 index 000000000..d46f146ea --- /dev/null +++ b/Sources/SmithyHTTPClientAPI/HttpClientConfiguration.swift @@ -0,0 +1,91 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AwsCommonRuntimeKit +import struct Foundation.TimeInterval +import enum Smithy.URIScheme +import struct SmithyHTTPAPI.Headers + +public class HTTPClientConfiguration { + + /// The timeout for establishing a connection, in seconds. + /// + /// If none is provided, the client will use default values based on the platform. + public var connectTimeout: TimeInterval? + + /// The timeout for socket, in seconds. + /// Sets maximum time to wait between two data packets. + /// Used to close stale connections that have no activity. + /// + /// Defaults to 60 seconds if no value is provided. + public var socketTimeout: TimeInterval + + /// HTTP headers to be submitted with every HTTP request. + /// + /// If none is provided, defaults to no extra headers. + public var defaultHeaders: Headers + + /// The maximum connections the HTTP client makes per host or per endpoint depending on the OS. + /// + /// For Apple platforms, it will be per host. + /// For Linux, it will be per endpoint (protocol + host + port). + public var maxConnections: Int + + // add any other properties here you want to give the service operations + // control over to be mapped to the Http Client + + /// The URL scheme to be used for HTTP requests. Supported values are `http` and `https`. + /// + /// If none is provided, the default protocol for the operation will be used + public var protocolType: URIScheme? + + /// Custom TLS configuration for HTTPS connections. + /// + /// Enables specifying client certificates and trust stores for secure communication. + /// Defaults to system's TLS settings if `nil`. + public var tlsConfiguration: (any TLSConfiguration)? + + /// HTTP Client Telemetry + public var telemetry: HttpTelemetry? + + /// Creates a configuration object for a SDK HTTP client. + /// + /// Not all configuration settings may be followed by all clients. + /// - Parameters: + /// - connectTimeout: The maximum time to wait for a connection to be established. + /// - socketTimeout: The maximum time to wait between data packets. + /// - defaultHeaders: HTTP headers to be included with every HTTP request. + /// Note that certain headers may cause your API request to fail. Defaults to no headers. + /// - protocolType: The HTTP scheme (`http` or `https`) to be used for API requests. Defaults to the operation's standard configuration. + /// - tlsConfiguration: Optional custom TLS configuration for HTTPS requests. If `nil`, defaults to a standard configuration. + /// - maxConnections: The maximum number of connections the HTTP client makes per host (for Apple platforms) or per endpoint (for Linux). For non-mac Apple platforms, defaults to 6. For macOS and Linux, defaults to 50. + public init( + connectTimeout: TimeInterval? = nil, + socketTimeout: TimeInterval = 60.0, + protocolType: URIScheme = .https, + defaultHeaders: Headers = Headers(), + tlsConfiguration: (any TLSConfiguration)? = nil, + telemetry: HttpTelemetry? = nil, + maxConnections: Int? = nil + ) { + self.socketTimeout = socketTimeout + self.protocolType = protocolType + self.defaultHeaders = defaultHeaders + self.connectTimeout = connectTimeout + self.tlsConfiguration = tlsConfiguration + self.telemetry = telemetry + if let maxConnections { + self.maxConnections = maxConnections + } else { + #if os(macOS) || os(Linux) + self.maxConnections = 50 + #else // iOS, ipadOS, watchOS, tvOS. + self.maxConnections = 6 // URLSession default. + #endif + } + } +} diff --git a/Sources/ClientRuntime/Networking/Http/HttpTelemetry.swift b/Sources/SmithyHTTPClientAPI/HttpTelemetry.swift similarity index 89% rename from Sources/ClientRuntime/Networking/Http/HttpTelemetry.swift rename to Sources/SmithyHTTPClientAPI/HttpTelemetry.swift index 900747f75..15f27d9a0 100644 --- a/Sources/ClientRuntime/Networking/Http/HttpTelemetry.swift +++ b/Sources/SmithyHTTPClientAPI/HttpTelemetry.swift @@ -7,6 +7,7 @@ import class Foundation.NSRecursiveLock import struct Smithy.AttributeKey import struct Smithy.Attributes import protocol SmithyHTTPAPI.HTTPClient +import SmithyTelemetryAPI /// Container for HTTPClient telemetry, including configurable attributes and names. /// @@ -33,21 +34,21 @@ public class HttpTelemetry: @unchecked Sendable { return attributes } - internal let contextManager: any TelemetryContextManager - internal let tracerProvider: any TracerProvider - internal let loggerProvider: any LoggerProvider + public let contextManager: any TelemetryContextManager + public let tracerProvider: any TracerProvider + public let loggerProvider: any LoggerProvider - internal let tracerScope: String - internal let spanName: String + public let tracerScope: String + public let spanName: String - internal let connectionsAcquireDuration: any Histogram + public let connectionsAcquireDuration: any Histogram private let connectionsLimit: any AsyncMeasurementHandle private let connectionsUsage: any AsyncMeasurementHandle - internal let connectionsUptime: any Histogram + public let connectionsUptime: any Histogram private let requestsUsage: any AsyncMeasurementHandle - internal let requestsQueuedDuration: any Histogram - internal let bytesSent: any MonotonicCounter - internal let bytesReceived: any MonotonicCounter + public let requestsQueuedDuration: any Histogram + public let bytesSent: any MonotonicCounter + public let bytesReceived: any MonotonicCounter // Lock to enforce exclusive access to non-Sendable properties private let lock = NSRecursiveLock() @@ -64,7 +65,7 @@ public class HttpTelemetry: @unchecked Sendable { private let _spanAttributes: Attributes? - var spanAttributes: Attributes? { + public var spanAttributes: Attributes? { lock.lock() defer { lock.unlock() } return _spanAttributes @@ -78,7 +79,7 @@ public class HttpTelemetry: @unchecked Sendable { return _httpMetricsUsage } - func updateHTTPMetricsUsage(_ block: (inout HttpMetricsUsage) -> Void) { + public func updateHTTPMetricsUsage(_ block: (inout HttpMetricsUsage) -> Void) { lock.lock() defer { lock.unlock() } block(&_httpMetricsUsage) @@ -86,7 +87,7 @@ public class HttpTelemetry: @unchecked Sendable { public init( httpScope: String, - telemetryProvider: any TelemetryProvider = DefaultTelemetry.provider, + telemetryProvider: any TelemetryProvider, meterScope: String? = nil, meterAttributes: Attributes? = nil, tracerScope: String? = nil, @@ -188,12 +189,12 @@ private enum RequestState { fileprivate static let queued = "queued" } -internal enum HttpMetricsAttributesKeys { +public enum HttpMetricsAttributesKeys { fileprivate static let state = AttributeKey(name: "state") - internal static let serverAddress = AttributeKey(name: "server.address") + public static let serverAddress = AttributeKey(name: "server.address") } -internal struct HttpMetricsUsage { +public struct HttpMetricsUsage { public var connectionsLimit: Int = 0 public var idleConnections: Int = 0 public var acquiredConnections: Int = 0 diff --git a/Sources/ClientRuntime/Networking/Http/CRT/TLSConfiguration.swift b/Sources/SmithyHTTPClientAPI/TLSConfiguration.swift similarity index 100% rename from Sources/ClientRuntime/Networking/Http/CRT/TLSConfiguration.swift rename to Sources/SmithyHTTPClientAPI/TLSConfiguration.swift diff --git a/Sources/SmithyReadWrite/ReadingClosure.swift b/Sources/SmithyReadWrite/ReadingClosure.swift index f09498a75..c3395aeec 100644 --- a/Sources/SmithyReadWrite/ReadingClosure.swift +++ b/Sources/SmithyReadWrite/ReadingClosure.swift @@ -153,7 +153,9 @@ public enum ReadingClosures { try reader.read() } - public static func readInt16(from reader: Reader) throws -> Int16? { + public static func readInt16( + from reader: Reader + ) throws -> Int16? { try reader.readIfPresent() } diff --git a/Sources/SmithyReadWrite/SmithyReader.swift b/Sources/SmithyReadWrite/SmithyReader.swift index 47819f431..5dcb23cab 100644 --- a/Sources/SmithyReadWrite/SmithyReader.swift +++ b/Sources/SmithyReadWrite/SmithyReader.swift @@ -44,7 +44,8 @@ public protocol SmithyReader: AnyObject { ) throws -> [Member]? /// Attempts to read a `null` value from the source document. - /// - Returns: `true` if the value read is null, `false` if a value is present but it is not null, `nil` if no value is present. + /// - Returns: `true` if the value read is null, `false` if a value is present but it is not null, + /// `nil` if no value is present. func readNullIfPresent() throws -> Bool? } diff --git a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClient.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift similarity index 73% rename from Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClient.swift rename to Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift index 90dd1c6f0..69761212c 100644 --- a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClient.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift @@ -10,57 +10,54 @@ import NIOCore import NIOHTTP1 import NIOPosix import NIOSSL -import struct Smithy.Attributes -import struct Smithy.SwiftLogger -import protocol Smithy.LogAgent -import struct SmithyHTTPAPI.Headers -import struct SmithyHTTPAPI.Header -import protocol SmithyHTTPAPI.HTTPClient -import class SmithyHTTPAPI.HTTPResponse -import class SmithyHTTPAPI.HTTPRequest -import enum SmithyHTTPAPI.HTTPStatusCode -import enum SmithyHTTPAPI.HTTPMethodType -import protocol Smithy.ReadableStream -import enum Smithy.ByteStream -import class SmithyStreams.BufferedStream +import Smithy +import SmithyHTTPAPI +import SmithyHTTPClientAPI +import SmithyStreams +import SmithyTelemetryAPI import struct Foundation.Date import struct Foundation.URLComponents import struct Foundation.URLQueryItem -import AwsCommonRuntimeKit /// AsyncHTTPClient-based HTTP client implementation that conforms to SmithyHTTPAPI.HTTPClient /// This implementation is thread-safe and supports concurrent request execution. -public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { - public static let noOpNIOHTTPClientTelemetry = HttpTelemetry( - httpScope: "NIOHTTPClient", - telemetryProvider: DefaultTelemetry.provider +public final class SwiftNIOHTTPClient: SmithyHTTPAPI.HTTPClient { + public static let noOpSwiftNIOHTTPClientTelemetry = + SmithyHTTPClientAPI.HttpTelemetry( + httpScope: "SwiftNIOHTTPClient", + telemetryProvider: SmithyTelemetryAPI.DefaultTelemetry.provider ) private let client: AsyncHTTPClient.HTTPClient - private let config: HttpClientConfiguration - private let tlsConfiguration: NIOHTTPClientTLSOptions? + private let config: SmithyHTTPClientAPI.HTTPClientConfiguration + private let tlsConfiguration: SwiftNIOHTTPClientTLSOptions? private let allocator: ByteBufferAllocator /// HTTP Client Telemetry - private let telemetry: HttpTelemetry + private let telemetry: SmithyHTTPClientAPI.HttpTelemetry /// Logger for HTTP-related events. private var logger: LogAgent - /// Creates a new `NIOHTTPClient`. + /// Creates a new `SwiftNIOHTTPClient`. /// - /// The client is created with its own internal `AsyncHTTPClient`, which is configured with system defaults. + /// The client is created with its own internal `AsyncHTTPClient`, + /// which is configured with system defaults. /// - Parameters: - /// - httpClientConfiguration: The configuration to use for the client's `AsyncHTTPClient` setup. + /// - httpClientConfiguration: The configuration to use for the + /// client's `AsyncHTTPClient` setup. /// - eventLoopGroup: The `EventLoopGroup` that the ``HTTPClient`` will use. public init( - httpClientConfiguration: HttpClientConfiguration, + httpClientConfiguration: SmithyHTTPClientAPI.HTTPClientConfiguration, eventLoopGroup: (any NIOCore.EventLoopGroup)? = nil ) { self.config = httpClientConfiguration - self.telemetry = httpClientConfiguration.telemetry ?? NIOHTTPClient.noOpNIOHTTPClientTelemetry - self.logger = self.telemetry.loggerProvider.getLogger(name: "NIOHTTPClient") - self.tlsConfiguration = httpClientConfiguration.tlsConfiguration as? NIOHTTPClientTLSOptions + self.telemetry = httpClientConfiguration.telemetry ?? + SwiftNIOHTTPClient.noOpSwiftNIOHTTPClientTelemetry + self.logger = self.telemetry.loggerProvider.getLogger( + name: "SwiftNIOHTTPClient" + ) + self.tlsConfiguration = httpClientConfiguration.tlsConfiguration as? SwiftNIOHTTPClientTLSOptions self.allocator = ByteBufferAllocator() var clientConfig = AsyncHTTPClient.HTTPClient.Configuration.from( @@ -80,7 +77,10 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { } if let eventLoopGroup { - self.client = AsyncHTTPClient.HTTPClient(eventLoopGroup: eventLoopGroup, configuration: clientConfig) + self.client = AsyncHTTPClient.HTTPClient( + eventLoopGroup: eventLoopGroup, + configuration: clientConfig + ) } else { self.client = AsyncHTTPClient.HTTPClient(configuration: clientConfig) } @@ -90,7 +90,9 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { try? client.syncShutdown() } - public func send(request: SmithyHTTPAPI.HTTPRequest) async throws -> SmithyHTTPAPI.HTTPResponse { + public func send( + request: SmithyHTTPAPI.HTTPRequest + ) async throws -> SmithyHTTPAPI.HTTPResponse { let telemetryContext = telemetry.contextManager.current() let tracer = telemetry.tracerProvider.getTracer( scope: telemetry.tracerScope @@ -102,7 +104,8 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { name: telemetry.spanName, initialAttributes: telemetry.spanAttributes, spanKind: SpanKind.internal, - parentContext: telemetryContext) + parentContext: telemetryContext + ) defer { span.end() } @@ -124,13 +127,15 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { telemetry.requestsQueuedDuration.record( value: queuedEnd - queuedStart, attributes: Attributes(), - context: telemetryContext) + context: telemetryContext + ) // END - smithy.client.http.requests.queued_duration // Update connection and request usage metrics telemetry.updateHTTPMetricsUsage { httpMetricsUsage in // TICK - smithy.client.http.connections.limit - // Note: AsyncHTTPClient doesn't expose connection pool configuration publicly + // Note: AsyncHTTPClient doesn't expose connection pool + // configuration publicly httpMetricsUsage.connectionsLimit = 0 // TICK - smithy.client.http.connections.usage @@ -149,12 +154,13 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { telemetry.connectionsUptime.record( value: Date().timeIntervalSinceReferenceDate - connectionUptimeStart, attributes: Attributes(), - context: telemetryContext) + context: telemetryContext + ) } let httpMethod = request.method.rawValue let url = request.destination.url - logger.debug("NIOHTTPClient(\(httpMethod) \(String(describing: url))) started") + logger.debug("SwiftNIOHTTPClient(\(httpMethod) \(String(describing: url))) started") logBodyDescription(request.body) do { @@ -162,22 +168,35 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { let nioResponse = try await client.execute(nioRequest, timeout: timeout) // Convert NIO response to Smithy HTTPResponse - let statusCode = HTTPStatusCode(rawValue: Int(nioResponse.status.code)) ?? .insufficientStorage + let statusCode = HTTPStatusCode( + rawValue: Int(nioResponse.status.code) + ) ?? .insufficientStorage var headers = Headers() for (name, value) in nioResponse.headers { headers.add(name: name, value: value) } - let body = await NIOHTTPClientStreamBridge.convertResponseBody(from: nioResponse) + let body = await SwiftNIOHTTPClientStreamBridge.convertResponseBody( + from: nioResponse + ) - let response = HTTPResponse(headers: headers, body: body, statusCode: statusCode) - logger.debug("NIOHTTPClient(\(httpMethod) \(String(describing: url))) succeeded") + let response = HTTPResponse( + headers: headers, + body: body, + statusCode: statusCode + ) + logger.debug("SwiftNIOHTTPClient(\(httpMethod) \(String(describing: url))) succeeded") return response } catch { let urlDescription = String(describing: url) let errorDescription = String(describing: error) - logger.error("NIOHTTPClient(\(httpMethod) \(urlDescription)) failed with error: \(errorDescription)") + logger.error( + """ + SwiftNIOHTTPClient(\(httpMethod) \(urlDescription)) \ + failed with error: \(errorDescription) + """ + ) throw error } } @@ -187,7 +206,8 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { from request: SmithyHTTPAPI.HTTPRequest ) async throws -> AsyncHTTPClient.HTTPClientRequest { var components = URLComponents() - components.scheme = config.protocolType?.rawValue ?? request.destination.scheme.rawValue + components.scheme = config.protocolType?.rawValue ?? + request.destination.scheme.rawValue components.host = request.endpoint.uri.host components.port = port(for: request) components.percentEncodedPath = request.destination.path @@ -196,7 +216,9 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { URLQueryItem(name: $0.name, value: $0.value) } } - guard let url = components.url else { throw NIOHTTPClientError.incompleteHTTPRequest } + guard let url = components.url else { + throw SwiftNIOHTTPClientError.incompleteHTTPRequest + } let method = NIOHTTP1.HTTPMethod(rawValue: request.method.rawValue) var nioRequest = AsyncHTTPClient.HTTPClientRequest(url: url.absoluteString) @@ -209,7 +231,7 @@ public final class NIOHTTPClient: SmithyHTTPAPI.HTTPClient { } } - nioRequest.body = try await NIOHTTPClientStreamBridge.convertRequestBody( + nioRequest.body = try await SwiftNIOHTTPClientStreamBridge.convertRequestBody( from: request.body, allocator: allocator ) diff --git a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientConfiguration+HTTPClientConfiguration.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientConfiguration+HTTPClientConfiguration.swift similarity index 78% rename from Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientConfiguration+HTTPClientConfiguration.swift rename to Sources/SmithySwiftNIO/SwiftNIOHTTPClientConfiguration+HTTPClientConfiguration.swift index 2d6df4e77..a543acdcf 100644 --- a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientConfiguration+HTTPClientConfiguration.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientConfiguration+HTTPClientConfiguration.swift @@ -7,10 +7,13 @@ import AsyncHTTPClient import NIOCore +import SmithyHTTPClientAPI extension HTTPClient.Configuration { - static func from(httpClientConfiguration: HttpClientConfiguration) -> HTTPClient.Configuration { + static func from( + httpClientConfiguration: SmithyHTTPClientAPI.HTTPClientConfiguration + ) -> HTTPClient.Configuration { let connect: TimeAmount? = httpClientConfiguration.connectTimeout != nil ? .seconds(Int64(httpClientConfiguration.connectTimeout!)) : nil @@ -24,7 +27,8 @@ extension HTTPClient.Configuration { let pool = HTTPClient.Configuration.ConnectionPool( idleTimeout: .seconds(60), // default - concurrentHTTP1ConnectionsPerHostSoftLimit: httpClientConfiguration.maxConnections + concurrentHTTP1ConnectionsPerHostSoftLimit: + httpClientConfiguration.maxConnections ) return .init( diff --git a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientError.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientError.swift similarity index 92% rename from Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientError.swift rename to Sources/SmithySwiftNIO/SwiftNIOHTTPClientError.swift index 90a4660c5..176b204b9 100644 --- a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientError.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientError.swift @@ -6,7 +6,7 @@ // /// Errors that are particular to the NIO-based Smithy HTTP client. -public enum NIOHTTPClientError: Error { +public enum SwiftNIOHTTPClientError: Error { /// A URL could not be formed from the `HTTPRequest`. /// Please file a bug with aws-sdk-swift if you experience this error. diff --git a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientStreamBridge.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientStreamBridge.swift similarity index 83% rename from Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientStreamBridge.swift rename to Sources/SmithySwiftNIO/SwiftNIOHTTPClientStreamBridge.swift index 89ca5dcea..7fb3da44c 100644 --- a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientStreamBridge.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientStreamBridge.swift @@ -12,14 +12,15 @@ import Smithy import SmithyStreams /// Handles streaming between Smithy streams and AsyncHTTPClient -final class NIOHTTPClientStreamBridge { +final class SwiftNIOHTTPClientStreamBridge { /// Convert Smithy ByteStream to AsyncHTTPClient request body static func convertRequestBody( from body: ByteStream, allocator: ByteBufferAllocator, chunkSize: Int = CHUNK_SIZE_BYTES - ) async throws -> AsyncHTTPClient.HTTPClientRequest.Body { + ) async throws + -> AsyncHTTPClient.HTTPClientRequest.Body { switch body { case .noStream: // No body to send @@ -37,7 +38,11 @@ final class NIOHTTPClientStreamBridge { case .stream(let stream): // Handle streaming request body - return try await convertStreamToRequestBody(stream: stream, allocator: allocator, chunkSize: chunkSize) + return try await convertStreamToRequestBody( + stream: stream, + allocator: allocator, + chunkSize: chunkSize + ) } } @@ -51,7 +56,10 @@ final class NIOHTTPClientStreamBridge { var iterator = response.body.makeAsyncIterator() while let buffer = try await iterator.next() { // Convert ByteBuffer to Data and write to buffered stream - if let bytes = buffer.getBytes(at: buffer.readerIndex, length: buffer.readableBytes) { + if let bytes = buffer.getBytes( + at: buffer.readerIndex, + length: buffer.readableBytes + ) { let data = Data(bytes) try bufferedStream.write(contentsOf: data) } @@ -70,7 +78,11 @@ final class NIOHTTPClientStreamBridge { allocator: ByteBufferAllocator, chunkSize: Int = CHUNK_SIZE_BYTES ) async throws -> AsyncHTTPClient.HTTPClientRequest.Body { - let asyncSequence = StreamToAsyncSequence(stream: stream, allocator: allocator, chunkSize: chunkSize) + let asyncSequence = StreamToAsyncSequence( + stream: stream, + allocator: allocator, + chunkSize: chunkSize + ) // Use known length if available, unless the stream is eligible for chunked streaming. if let length = stream.length, !stream.isEligibleForChunkedStreaming { @@ -89,7 +101,11 @@ internal struct StreamToAsyncSequence: AsyncSequence, Sendable { private let allocator: ByteBufferAllocator private let chunkSize: Int - init(stream: Smithy.Stream, allocator: ByteBufferAllocator, chunkSize: Int = CHUNK_SIZE_BYTES) { + init( + stream: Smithy.Stream, + allocator: ByteBufferAllocator, + chunkSize: Int = CHUNK_SIZE_BYTES + ) { self.stream = stream self.allocator = allocator self.chunkSize = chunkSize @@ -130,7 +146,7 @@ internal struct StreamToAsyncSequence: AsyncSequence, Sendable { } catch { isFinished = true stream.close() - throw NIOHTTPClientError.streamingError(error) + throw SwiftNIOHTTPClientError.streamingError(error) } } } diff --git a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientTLSOptions.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSOptions.swift similarity index 92% rename from Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientTLSOptions.swift rename to Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSOptions.swift index c4b40fe54..f7af43ff3 100644 --- a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientTLSOptions.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSOptions.swift @@ -7,8 +7,9 @@ import Foundation import NIOSSL +import SmithyHTTPClientAPI -public struct NIOHTTPClientTLSOptions: TLSConfiguration, Sendable { +public struct SwiftNIOHTTPClientTLSOptions: SmithyHTTPClientAPI.TLSConfiguration, Sendable { /// Optional path to a PEM certificate public var certificate: String? diff --git a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientTLSResolverUtils.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSResolverUtils.swift similarity index 66% rename from Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientTLSResolverUtils.swift rename to Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSResolverUtils.swift index f404d3152..3ec4f2d27 100644 --- a/Sources/ClientRuntime/Networking/Http/NIO/NIOHTTPClientTLSResolverUtils.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClientTLSResolverUtils.swift @@ -8,7 +8,7 @@ import Foundation import NIOSSL -extension NIOHTTPClientTLSOptions { +extension SwiftNIOHTTPClientTLSOptions { func makeNIOSSLConfiguration() throws -> NIOSSL.TLSConfiguration { var tlsConfig = NIOSSL.TLSConfiguration.makeClientConfiguration() @@ -16,22 +16,29 @@ extension NIOHTTPClientTLSOptions { if useSelfSignedCertificate { if let certificateDir = certificateDir, let certificate = certificate { let certificatePath = "\(certificateDir)/\(certificate)" - let certificates = try NIOHTTPClientTLSOptions.loadCertificates(from: certificatePath) + let certificates = try SwiftNIOHTTPClientTLSOptions.loadCertificates(from: certificatePath) tlsConfig.trustRoots = .certificates(certificates) } else if let certificate = certificate { - let certificates = try NIOHTTPClientTLSOptions.loadCertificates(from: certificate) + let certificates = try SwiftNIOHTTPClientTLSOptions.loadCertificates(from: certificate) tlsConfig.trustRoots = .certificates(certificates) } } if useProvidedKeystore { if let pkcs12Path = pkcs12Path, let pkcs12Password = pkcs12Password { - let bundle = try NIOHTTPClientTLSOptions.loadPKCS12Bundle(from: pkcs12Path, password: pkcs12Password) + let bundle = try SwiftNIOHTTPClientTLSOptions.loadPKCS12Bundle( + from: pkcs12Path, + password: pkcs12Password + ) tlsConfig.certificateChain = bundle.certificateChain.map { .certificate($0) } tlsConfig.privateKey = .privateKey(bundle.privateKey) } else if let certificate = certificate, let privateKey = privateKey { - let cert = try NIOHTTPClientTLSOptions.loadCertificate(from: certificate) - let key = try NIOHTTPClientTLSOptions.loadPrivateKey(from: privateKey) + let cert = try SwiftNIOHTTPClientTLSOptions.loadCertificate( + from: certificate + ) + let key = try SwiftNIOHTTPClientTLSOptions.loadPrivateKey( + from: privateKey + ) tlsConfig.certificateChain = [.certificate(cert)] tlsConfig.privateKey = .privateKey(key) } @@ -41,7 +48,7 @@ extension NIOHTTPClientTLSOptions { } } -extension NIOHTTPClientTLSOptions { +extension SwiftNIOHTTPClientTLSOptions { static func loadCertificates(from filePath: String) throws -> [NIOSSLCertificate] { let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath)) @@ -51,7 +58,7 @@ extension NIOHTTPClientTLSOptions { static func loadCertificate(from filePath: String) throws -> NIOSSLCertificate { let certificates = try loadCertificates(from: filePath) guard let certificate = certificates.first else { - throw NIOHTTPClientTLSError.noCertificateFound(filePath) + throw SwiftNIOHTTPClientTLSError.noCertificateFound(filePath) } return certificate } @@ -61,16 +68,19 @@ extension NIOHTTPClientTLSOptions { return try NIOSSLPrivateKey(bytes: Array(fileData), format: .pem) } - static func loadPKCS12Bundle(from filePath: String, password: String) throws -> NIOSSLPKCS12Bundle { + static func loadPKCS12Bundle( + from filePath: String, + password: String + ) throws -> NIOSSLPKCS12Bundle { do { return try NIOSSLPKCS12Bundle(file: filePath, passphrase: password.utf8) } catch { - throw NIOHTTPClientTLSError.invalidPKCS12(filePath, underlying: error) + throw SwiftNIOHTTPClientTLSError.invalidPKCS12(filePath, underlying: error) } } } -public enum NIOHTTPClientTLSError: Error, LocalizedError { +public enum SwiftNIOHTTPClientTLSError: Error, LocalizedError { case noCertificateFound(String) case invalidPKCS12(String, underlying: Error) @@ -79,7 +89,10 @@ public enum NIOHTTPClientTLSError: Error, LocalizedError { case .noCertificateFound(let path): return "No certificate found at path: \(path)" case .invalidPKCS12(let path, let underlying): - return "Failed to load PKCS#12 file at path: \(path). Error: \(String(describing: underlying))" + return """ + Failed to load PKCS#12 file at path: \(path). \ + Error: \(String(describing: underlying)) + """ } } } diff --git a/Sources/SmithyTelemetryAPI/Context/TelemetryContext.swift b/Sources/SmithyTelemetryAPI/Context/TelemetryContext.swift new file mode 100644 index 000000000..de1ac42d1 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Context/TelemetryContext.swift @@ -0,0 +1,18 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// A Telemetry Context is a container that can be associated with Tracers and Metrics. +/// +/// Context implementations may be containers for execution-scoped values across API boundaries (both in-process and +/// distributed). +public protocol TelemetryContext: Sendable { + + /// Make this context the currently active context. + /// + /// - Returns: the scope of the current context + func makeCurrent() -> TelemetryScope +} diff --git a/Sources/SmithyTelemetryAPI/Context/TelemetryContextManager.swift b/Sources/SmithyTelemetryAPI/Context/TelemetryContextManager.swift new file mode 100644 index 000000000..2954d78df --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Context/TelemetryContextManager.swift @@ -0,0 +1,15 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// A Telemetry Context Manager manages the Telemetry Contexts in a client. +/// +/// Implementations should be able to manage contexts in a thread-safe way. +public protocol TelemetryContextManager: Sendable { + + /// - Returns: the current Telemetry Context + func current() -> TelemetryContext +} diff --git a/Sources/SmithyTelemetryAPI/Context/TelemetryScope.swift b/Sources/SmithyTelemetryAPI/Context/TelemetryScope.swift new file mode 100644 index 000000000..a0fa77c3f --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Context/TelemetryScope.swift @@ -0,0 +1,13 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Delineates a Telemetry Scope that has a beginning and end, particularly for Telemetry Contexts. +public protocol TelemetryScope: Sendable { + + /// Ends the scope. + func end() +} diff --git a/Sources/SmithyTelemetryAPI/DefaultTelemetry.swift b/Sources/SmithyTelemetryAPI/DefaultTelemetry.swift new file mode 100644 index 000000000..7afe5570a --- /dev/null +++ b/Sources/SmithyTelemetryAPI/DefaultTelemetry.swift @@ -0,0 +1,170 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.AttributeKey +import struct Smithy.Attributes +import protocol Smithy.LogAgent +import struct Smithy.SwiftLogger + +/// Namespace for the Default SDK Telemetry implementations. +public enum DefaultTelemetry: Sendable { + /// The Default SDK Telemetry Provider Implementation. + /// + /// - contextManager: no-op + /// - loggerProvider: provides SwiftLoggers + /// - meterProvider: no-op + /// - tracerProvider: no-op + public static let provider: TelemetryProvider = _DefaultTelemetryProvider() + + fileprivate final class _DefaultTelemetryProvider: TelemetryProvider { + let contextManager: TelemetryContextManager = defaultContextManager + let loggerProvider: LoggerProvider = defaultLoggerProvider + let meterProvider: MeterProvider = defaultMeterProvider + let tracerProvider: TracerProvider = defaultTracerProvider + } +} + +// Context +extension DefaultTelemetry { + public static let defaultContextManager: TelemetryContextManager = NoOpTelemetryContextManager() + fileprivate static let defaultTelemetryContext: TelemetryContext = NoOpTelemetryContext() + fileprivate static let defaultTelemetryScope: TelemetryScope = NoOpTelemetryScope() + + fileprivate final class NoOpTelemetryContextManager: TelemetryContextManager { + func current() -> TelemetryContext { defaultTelemetryContext } + } + + fileprivate final class NoOpTelemetryContext: TelemetryContext { + func makeCurrent() -> TelemetryScope { defaultTelemetryScope } + } + + fileprivate final class NoOpTelemetryScope: TelemetryScope { + func end() {} + } +} + +// Logging +extension DefaultTelemetry { + public static let defaultLoggerProvider: LoggerProvider = _DefaultLoggerProvider() + + fileprivate final class _DefaultLoggerProvider: LoggerProvider { + func getLogger(name: String) -> LogAgent { SwiftLogger(label: name) } + } +} + +// Metrics +extension DefaultTelemetry { + public static let defaultMeterProvider: MeterProvider = NoOpMeterProvider() + fileprivate static let defaultMeter: Meter = NoOpMeter() + fileprivate static let defaultAsyncMeasurementHandle: AsyncMeasurementHandle = NoOpAsyncMeasurementHandle() + fileprivate static let defaultUpDownCounter: UpDownCounter = NoOpUpDownCounter() + fileprivate static let defaultMonotonicCounter: MonotonicCounter = NoOpMonotonicCounter() + fileprivate static let defaultHistogram: Histogram = NoOpHistogram() + + fileprivate final class NoOpMeterProvider: MeterProvider { + func getMeter(scope: String, attributes: Attributes?) -> Meter { defaultMeter } + } + + fileprivate final class NoOpMeter: Meter { + func createGauge( + name: String, + callback: @escaping (any DoubleAsyncMeasurement) -> Void, + units: String?, + description: String? + ) -> AsyncMeasurementHandle { + defaultAsyncMeasurementHandle + } + + func createUpDownCounter( + name: String, + units: String?, + description: String? + ) -> UpDownCounter { + defaultUpDownCounter + } + + func createAsyncUpDownCounter( + name: String, + callback: @escaping (any LongAsyncMeasurement) -> Void, + units: String?, + description: String? + ) -> AsyncMeasurementHandle { + defaultAsyncMeasurementHandle + } + + func createCounter( + name: String, + units: String?, + description: String? + ) -> MonotonicCounter { + defaultMonotonicCounter + } + + func createAsyncMonotonicCounter( + name: String, + callback: @escaping (any LongAsyncMeasurement) -> Void, + units: String?, + description: String? + ) -> AsyncMeasurementHandle { + defaultAsyncMeasurementHandle + } + + func createHistogram( + name: String, + units: String?, + description: String? + ) -> Histogram { + defaultHistogram + } + } + + fileprivate final class NoOpAsyncMeasurementHandle: AsyncMeasurementHandle { + func stop() {} + } + + fileprivate final class NoOpUpDownCounter: UpDownCounter { + func add(value: Int, attributes: Attributes?, context: TelemetryContext?) {} + } + + fileprivate final class NoOpMonotonicCounter: MonotonicCounter { + func add(value: Int, attributes: Attributes?, context: TelemetryContext?) {} + } + + fileprivate final class NoOpHistogram: Histogram { + func record(value: Double, attributes: Attributes?, context: TelemetryContext?) {} + } +} + +// Trace +extension DefaultTelemetry { + public static let defaultTracerProvider: TracerProvider = NoOpTracerProvider() + fileprivate static let defaultTracer: Tracer = NoOpTracer() + fileprivate static let defaultTraceSpan: TraceSpan = NoOpTraceSpan() + + fileprivate final class NoOpTracerProvider: TracerProvider { + func getTracer(scope: String) -> Tracer { defaultTracer } + } + + fileprivate final class NoOpTracer: Tracer { + func createSpan( + name: String, + initialAttributes: Attributes?, + spanKind: SpanKind, + parentContext: TelemetryContext? + ) -> TraceSpan { + defaultTraceSpan + } + } + + fileprivate final class NoOpTraceSpan: TraceSpan { + let name: String = "" + func emitEvent(name: String, attributes: Attributes?) {} + func setAttribute(key: AttributeKey, value: T) {} + func setStatus(status: TraceSpanStatus) {} + func end() {} + } +} diff --git a/Sources/SmithyTelemetryAPI/Logging/LoggerProvider.swift b/Sources/SmithyTelemetryAPI/Logging/LoggerProvider.swift new file mode 100644 index 000000000..4a2627dca --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Logging/LoggerProvider.swift @@ -0,0 +1,17 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import protocol Smithy.LogAgent + +/// A Logger Provider provides implementations of LogAgents. +public protocol LoggerProvider: Sendable { + /// Provides a LogAgent. + /// + /// - Parameter name: the name associated with the LogAgent + /// - Returns: a LogAgent + func getLogger(name: String) -> LogAgent +} diff --git a/Sources/SmithyTelemetryAPI/Metrics/AsyncMeasurement.swift b/Sources/SmithyTelemetryAPI/Metrics/AsyncMeasurement.swift new file mode 100644 index 000000000..25e9faa3f --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Metrics/AsyncMeasurement.swift @@ -0,0 +1,33 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// Handle to stop recording values of an AsyncMeasurement. +public protocol AsyncMeasurementHandle: Sendable { + + /// Stop recording values of an AsyncMeasurement. + /// + /// Implementations probably will unregister an AsyncMeasurement callback. + func stop() +} + +public typealias LongAsyncMeasurement = AsyncMeasurement + +public typealias DoubleAsyncMeasurement = AsyncMeasurement + +/// Async measurement of a specific numeric type. +public protocol AsyncMeasurement { + associatedtype NumericType: Numeric + + /// Asynchronously records a value for a metric, usually as a callback to an async instrument created by a Meter. + /// + /// - Parameter value: value to record + /// - Parameter attributes: associated attributes, typically of the metric + /// - Parameter context: context in which value is recorded in + func record(value: NumericType, attributes: Attributes?, context: TelemetryContext?) +} diff --git a/Sources/SmithyTelemetryAPI/Metrics/Histogram.swift b/Sources/SmithyTelemetryAPI/Metrics/Histogram.swift new file mode 100644 index 000000000..9aeb869d1 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Metrics/Histogram.swift @@ -0,0 +1,21 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// A Histogram measures a value where the statistics are likely meaningful. +/// +/// Examples include: request latency, HTTP response times +public protocol Histogram: Sendable { + + /// Records a value for a metric. + /// + /// - Parameter value: value to record + /// - Parameter attributes: associated attributes, typically of the metric + /// - Parameter context: context in which value is recorded in + func record(value: Double, attributes: Attributes?, context: TelemetryContext?) +} diff --git a/Sources/SmithyTelemetryAPI/Metrics/Meter.swift b/Sources/SmithyTelemetryAPI/Metrics/Meter.swift new file mode 100644 index 000000000..879dcddbf --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Metrics/Meter.swift @@ -0,0 +1,111 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// A Meter is the entry point to create the instruments. +/// +/// The following instrument creation entry points are defined: +/// +/// - Gauge +/// - UpDownCounter +/// - Asynchronous UpDownCounter +/// - MonotonicCounter +/// - Asynchronous MonotonicCounter +/// - Histogram +public protocol Meter: Sendable { + + /// Creates a Gauge, used to measure the current instantaneous value of something. + /// + /// Examples include: the current memory used by a process + /// + /// - Parameter name: the instrument name + /// - Parameter callback: callback invoked when a Gauge value is read + /// - Parameter units: the unit of measure + /// - Parameter description: a description of the metric + /// - Returns: handle to stop recording Gauge metrics + func createGauge( + name: String, + callback: @escaping (any DoubleAsyncMeasurement) -> Void, + units: String?, + description: String? + ) -> AsyncMeasurementHandle + + /// Creates an UpDownCounter, used to measure a value that goes up or down. + /// + /// Examples include: queue length + /// + /// - Parameter name: the instrument name + /// - Parameter units: the unit of measure + /// - Parameter description: a description of the metric + /// - Returns: an UpDownCounter + func createUpDownCounter( + name: String, + units: String?, + description: String? + ) -> UpDownCounter + + /// Creates an asynchronous UpDownCounter, used to measure a value that goes up or down. + /// + /// Use an asynchronous UpDownCounter instead of a synchronous UpDownCounter when only absolute values are + /// available, or it would otherwise be expensive to keep track of continuously. + /// + /// - Parameter name: the instrument name + /// - Parameter callback: callback invoked when an UpDownCounter value is recorded + /// - Parameter units: the unit of measure + /// - Parameter description: a description of the metric + /// - Returns: handle to stop recording UpDownCounter metrics + func createAsyncUpDownCounter( + name: String, + callback: @escaping (any LongAsyncMeasurement) -> Void, + units: String?, + description: String? + ) -> AsyncMeasurementHandle + + /// Creates a MonotonicCounter, used to measure a value that only ever increases. + /// + /// Examples include: total requests received + /// + /// - Parameter name: the instrument name + /// - Parameter units: the unit of measure + /// - Parameter description: a description of the metric + /// - Returns: a MonotonicCounter + func createCounter( + name: String, + units: String?, + description: String? + ) -> MonotonicCounter + + /// Creates an asynchronous MonotonicCounter, used to measure a value that only ever increases. + /// + /// Use an asynchronous MonotonicCounter instead of a synchronous MonotonicCounter when only absolute values are + /// available, or it would otherwise be expensive to keep track of continuously. + /// + /// - Parameter name: the instrument name + /// - Parameter callback: callback invoked when an MonotonicCounter value is recorded + /// - Parameter units: the unit of measure + /// - Parameter description: a description of the metric + /// - Returns: handle to stop recording MonotonicCounter metrics + func createAsyncMonotonicCounter( + name: String, + callback: @escaping (any LongAsyncMeasurement) -> Void, + units: String?, + description: String? + ) -> AsyncMeasurementHandle + + /// Creates a Histogram, used to measure a value where the statistics are likely meaningful. + /// + /// Examples include: request latency, HTTP response times + /// + /// - Parameter name: the instrument name + /// - Parameter units: the unit of measure + /// - Parameter description: a description of the metric + /// - Returns: a Histogram + func createHistogram( + name: String, + units: String?, + description: String? + ) -> Histogram +} diff --git a/Sources/SmithyTelemetryAPI/Metrics/MeterProvider.swift b/Sources/SmithyTelemetryAPI/Metrics/MeterProvider.swift new file mode 100644 index 000000000..39f327138 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Metrics/MeterProvider.swift @@ -0,0 +1,19 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// A Meter Provider provides implementations of Meters. +public protocol MeterProvider: Sendable { + + /// Provides a Meter. + /// + /// - Parameter scope: the name of the instrumentation scope that uniquely identifies this meter + /// - Parameter attributes: instrumentation scope attributes to associate with emitted telemetry data + /// - Returns: a Meter + func getMeter(scope: String, attributes: Attributes?) -> Meter +} diff --git a/Sources/SmithyTelemetryAPI/Metrics/MonotonicCounter.swift b/Sources/SmithyTelemetryAPI/Metrics/MonotonicCounter.swift new file mode 100644 index 000000000..684aa1693 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Metrics/MonotonicCounter.swift @@ -0,0 +1,21 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// A Monotonic Counter measures a value that only ever increases. +/// +/// Examples include: total requests received +public protocol MonotonicCounter: Sendable { + + /// Records a value for a metric. + /// + /// - Parameter value: value to record + /// - Parameter attributes: associated attributes, typically of the metric + /// - Parameter context: context in which value is recorded in + func add(value: Int, attributes: Attributes?, context: TelemetryContext?) +} diff --git a/Sources/SmithyTelemetryAPI/Metrics/UpDownCounter.swift b/Sources/SmithyTelemetryAPI/Metrics/UpDownCounter.swift new file mode 100644 index 000000000..7bc17d005 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Metrics/UpDownCounter.swift @@ -0,0 +1,21 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// An UpDownCounter measures a value that goes up or down. +/// +/// Examples include: queue length +public protocol UpDownCounter: Sendable { + + /// Records a value for a metric. + /// + /// - Parameter value: value to record + /// - Parameter attributes: associated attributes, typically of the metric + /// - Parameter context: context in which value is recorded in + func add(value: Int, attributes: Attributes?, context: TelemetryContext?) +} diff --git a/Sources/SmithyTelemetryAPI/TelemetryProvider.swift b/Sources/SmithyTelemetryAPI/TelemetryProvider.swift new file mode 100644 index 000000000..4e4d5d867 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/TelemetryProvider.swift @@ -0,0 +1,18 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// A Telemetry Provider provides the SDK-level Telemetry configuration values. +public protocol TelemetryProvider: Sendable { + /// The configured Telemetry Context Manager. + var contextManager: any TelemetryContextManager { get } + /// The configured Logger Provider. + var loggerProvider: any LoggerProvider { get } + /// The configured Meter Provider. + var meterProvider: any MeterProvider { get } + /// The configured Tracer Provider. + var tracerProvider: any TracerProvider { get } +} diff --git a/Sources/SmithyTelemetryAPI/Tracing/SpanKind.swift b/Sources/SmithyTelemetryAPI/Tracing/SpanKind.swift new file mode 100644 index 000000000..4342070bc --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Tracing/SpanKind.swift @@ -0,0 +1,23 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// The Kind of Trace Span, which indicates whether a span is a remote child or parent, and whether it is asynchronous. +public enum SpanKind { + /// An `internal` span is the default kind, which represents an internal operation within a client. + case `internal` + /// A `client` span represents a request to a remote service. This span is often the parent of a remote `server` + /// span and does not end until a response is received. + case client + /// A `server` span represents handling a synchronous network request. This span is often the child of a remote + /// `client` span that is expected to wait for the response. + case server + /// A `producer` span represents an asynchronous request. This span is a parent to a corresponding child `consumer` + /// span and may start and/or end before the child consumer span does. + case producer + /// A `consumer` span repesents a child of an asynchronous `producer` request. + case consumer +} diff --git a/Sources/SmithyTelemetryAPI/Tracing/TraceSpan.swift b/Sources/SmithyTelemetryAPI/Tracing/TraceSpan.swift new file mode 100644 index 000000000..4bd55bb2c --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Tracing/TraceSpan.swift @@ -0,0 +1,38 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.AttributeKey +import struct Smithy.Attributes + +/// A Trace Span represents a single unit of work which has a beginning and end time, and may be connected to other +/// spans as part of a parent / child relationship. +/// +/// A span with no parent is considered a root span. +public protocol TraceSpan: TelemetryScope { + + /// The name of the span. + var name: String { get } + + /// Emits an event to the span. + /// + /// - Parameter name: event name + /// - Parameter attributes: event attributes + func emitEvent(name: String, attributes: Attributes?) + + /// Set the value for the given attribute key. + /// + /// Implementations will want to restrict the value type to the allowed types. + /// + /// - Parameter key: attribute key + /// - Parameter value: attribute value + func setAttribute(key: AttributeKey, value: T) + + /// Sets the span status. + /// + /// - Parameter status: span status to set + func setStatus(status: TraceSpanStatus) +} diff --git a/Sources/SmithyTelemetryAPI/Tracing/TraceSpanStatus.swift b/Sources/SmithyTelemetryAPI/Tracing/TraceSpanStatus.swift new file mode 100644 index 000000000..a3fd46fd0 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Tracing/TraceSpanStatus.swift @@ -0,0 +1,16 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Status of a Trace Span. +public enum TraceSpanStatus { + /// The `unset` status is the default / implicit status. + case unset + /// The `ok` status indicates the operation is successful. + case ok + /// The `error` status indicates the operation is unsuccessful. + case error +} diff --git a/Sources/SmithyTelemetryAPI/Tracing/Tracer.swift b/Sources/SmithyTelemetryAPI/Tracing/Tracer.swift new file mode 100644 index 000000000..9752846b3 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Tracing/Tracer.swift @@ -0,0 +1,29 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// A Tracer is the entry point for creating spans. +/// +/// Implementations MAY provide convenient builder APIs or other extensions to create spans, but ultimately new spans +/// should be created using the tracer interface. +public protocol Tracer: Sendable { + + /// Create a new Trace Span. + /// + /// - Parameter name: name of the span + /// - Parameter initialAttributes: initial span attributes + /// - Parameter spanKind: kind of span + /// - Parameter parentContext: parent context of the span + /// - Returns: returns the new Trace Span + func createSpan( + name: String, + initialAttributes: Attributes?, + spanKind: SpanKind, + parentContext: TelemetryContext? + ) -> TraceSpan +} diff --git a/Sources/SmithyTelemetryAPI/Tracing/TracerProvider.swift b/Sources/SmithyTelemetryAPI/Tracing/TracerProvider.swift new file mode 100644 index 000000000..d9cdf7110 --- /dev/null +++ b/Sources/SmithyTelemetryAPI/Tracing/TracerProvider.swift @@ -0,0 +1,18 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Smithy.Attributes + +/// A Tracer Provider provides implementations of Tracers. +public protocol TracerProvider: Sendable { + + /// Gets a scoped Tracer. + /// + /// - Parameter scope: the unique scope of the Tracer + /// - Returns: a Tracer + func getTracer(scope: String) -> Tracer +} diff --git a/Sources/SmithyTestUtil/TestOrchestrator.swift b/Sources/SmithyTestUtil/TestOrchestrator.swift index 211e542cb..48e8d330e 100644 --- a/Sources/SmithyTestUtil/TestOrchestrator.swift +++ b/Sources/SmithyTestUtil/TestOrchestrator.swift @@ -8,6 +8,7 @@ import ClientRuntime import Smithy import SmithyHTTPAPI +import SmithyTelemetryAPI public struct TestOrchestrator { /// - Returns: An OrchestratorBuilder set up with defaults for selectAuthScheme and telemetry. diff --git a/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift b/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift index a365f0efa..debeafcce 100644 --- a/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift +++ b/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift @@ -17,6 +17,8 @@ import SmithyTestUtil import enum Smithy.LogAgentLevel import protocol Smithy.LogAgent import class SmithyStreams.BufferedStream +import class SmithyHTTPClientAPI.HttpTelemetry +import SmithyTelemetryAPI class FoundationStreamBridgeTests: XCTestCase { @@ -70,7 +72,10 @@ class FoundationStreamBridgeTests: XCTestCase { bridgeBufferSize: bridgeBufferSize, boundStreamBufferSize: boundStreamBufferSize, logger: TestLogger(), - telemetry: HttpTelemetry(httpScope: "FoundationStreamBridgeTests")) + telemetry: HttpTelemetry( + httpScope: "FoundationStreamBridgeTests", + telemetryProvider: DefaultTelemetry.provider + )) await subject.open() // This will hold the data that is bridged from the ReadableStream to the Foundation InputStream @@ -78,7 +83,7 @@ class FoundationStreamBridgeTests: XCTestCase { // Open the input stream & read it to either end-of-data or a stream error subject.inputStream.open() - while ![.atEnd, .error].contains(subject.inputStream.streamStatus) { + while ![Stream.Status.atEnd, Stream.Status.error].contains(subject.inputStream.streamStatus) { // Copy the input stream to the temp buffer. When count is positive, bytes were read let count = subject.inputStream.read(tempBuffer, maxLength: tempBufferSize) diff --git a/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift b/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift index 8cb8135df..df4fe7b8c 100644 --- a/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift +++ b/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift @@ -16,6 +16,7 @@ import SmithyRetries @_spi(SmithyReadWrite) import SmithyJSON @_spi(SmithyReadWrite) import SmithyReadWrite import SmithyStreams +import SmithyTelemetryAPI class OrchestratorTests: XCTestCase { struct TestInput { diff --git a/Tests/ClientRuntimeTests/NetworkingTests/Http/NIO/NIOHTTPClientStreamBridgeTests.swift b/Tests/SmithySwiftNIOTests/SwiftNIOHTTPClientStreamBridgeTests.swift similarity index 90% rename from Tests/ClientRuntimeTests/NetworkingTests/Http/NIO/NIOHTTPClientStreamBridgeTests.swift rename to Tests/SmithySwiftNIOTests/SwiftNIOHTTPClientStreamBridgeTests.swift index 9f64d9e45..1b36d4192 100644 --- a/Tests/ClientRuntimeTests/NetworkingTests/Http/NIO/NIOHTTPClientStreamBridgeTests.swift +++ b/Tests/SmithySwiftNIOTests/SwiftNIOHTTPClientStreamBridgeTests.swift @@ -14,9 +14,9 @@ import class SmithyStreams.BufferedStream import enum Smithy.LogAgentLevel import protocol Smithy.LogAgent import enum Smithy.ByteStream -@testable import ClientRuntime +@testable import SmithySwiftNIO -class NIOHTTPClientStreamBridgeTests: XCTestCase { +class SwiftNIOHTTPClientStreamBridgeTests: XCTestCase { let allocator = ByteBufferAllocator() func test_convertResponseBody_streamsAllDataCorrectly() async throws { @@ -55,7 +55,7 @@ class NIOHTTPClientStreamBridgeTests: XCTestCase { body: .bytes(buffer) ) - let resultStream = await NIOHTTPClientStreamBridge.convertResponseBody(from: response) + let resultStream = await SwiftNIOHTTPClientStreamBridge.convertResponseBody(from: response) let convertedData = try await readAllData(from: resultStream) XCTAssertEqual(convertedData, originalData, @@ -66,7 +66,7 @@ class NIOHTTPClientStreamBridgeTests: XCTestCase { func test_convertRequestBody_withNoStream() async throws { let byteStream = ByteStream.noStream - let result = try await NIOHTTPClientStreamBridge.convertRequestBody( + let result = try await SwiftNIOHTTPClientStreamBridge.convertRequestBody( from: byteStream, allocator: allocator ) @@ -84,7 +84,7 @@ class NIOHTTPClientStreamBridgeTests: XCTestCase { let testData = "Hello, World!".data(using: .utf8)! let byteStream = ByteStream.data(testData) - let result = try await NIOHTTPClientStreamBridge.convertRequestBody( + let result = try await SwiftNIOHTTPClientStreamBridge.convertRequestBody( from: byteStream, allocator: allocator ) @@ -111,7 +111,7 @@ class NIOHTTPClientStreamBridgeTests: XCTestCase { let bufferedStream = BufferedStream(data: testData, isClosed: true) let byteStream = ByteStream.stream(bufferedStream) - let result = try await NIOHTTPClientStreamBridge.convertRequestBody( + let result = try await SwiftNIOHTTPClientStreamBridge.convertRequestBody( from: byteStream, allocator: allocator, chunkSize: 100 // try a non-default chunk size diff --git a/Tests/ClientRuntimeTests/NetworkingTests/Http/NIO/NIOHTTPClientTLSOptionsTests.swift b/Tests/SmithySwiftNIOTests/SwiftNIOHTTPClientTLSOptionsTests.swift similarity index 81% rename from Tests/ClientRuntimeTests/NetworkingTests/Http/NIO/NIOHTTPClientTLSOptionsTests.swift rename to Tests/SmithySwiftNIOTests/SwiftNIOHTTPClientTLSOptionsTests.swift index e26d8db1f..fd64ab5f1 100644 --- a/Tests/ClientRuntimeTests/NetworkingTests/Http/NIO/NIOHTTPClientTLSOptionsTests.swift +++ b/Tests/SmithySwiftNIOTests/SwiftNIOHTTPClientTLSOptionsTests.swift @@ -7,12 +7,12 @@ import Foundation import XCTest -@testable import ClientRuntime +@testable import SmithySwiftNIO -class NIOHTTPClientTLSOptionsTests: XCTestCase { +class SwiftNIOHTTPClientTLSOptionsTests: XCTestCase { func test_init_withDefaults() { - let tlsOptions = NIOHTTPClientTLSOptions() + let tlsOptions = SwiftNIOHTTPClientTLSOptions() XCTAssertNil(tlsOptions.certificate) XCTAssertNil(tlsOptions.certificateDir) @@ -24,7 +24,7 @@ class NIOHTTPClientTLSOptionsTests: XCTestCase { } func test_init_withCertificate() { - let tlsOptions = NIOHTTPClientTLSOptions(certificate: "/path/to/cert.pem") + let tlsOptions = SwiftNIOHTTPClientTLSOptions(certificate: "/path/to/cert.pem") XCTAssertEqual(tlsOptions.certificate, "/path/to/cert.pem") XCTAssertTrue(tlsOptions.useSelfSignedCertificate) @@ -32,7 +32,7 @@ class NIOHTTPClientTLSOptionsTests: XCTestCase { } func test_init_withCertificateDir() { - let tlsOptions = NIOHTTPClientTLSOptions(certificateDir: "/path/to/certs/") + let tlsOptions = SwiftNIOHTTPClientTLSOptions(certificateDir: "/path/to/certs/") XCTAssertEqual(tlsOptions.certificateDir, "/path/to/certs/") XCTAssertTrue(tlsOptions.useSelfSignedCertificate) @@ -40,7 +40,7 @@ class NIOHTTPClientTLSOptionsTests: XCTestCase { } func test_init_withPKCS12() { - let tlsOptions = NIOHTTPClientTLSOptions( + let tlsOptions = SwiftNIOHTTPClientTLSOptions( pkcs12Path: "/path/to/cert.p12", pkcs12Password: "password" ) @@ -52,7 +52,7 @@ class NIOHTTPClientTLSOptionsTests: XCTestCase { } func test_init_withCertificateAndPrivateKey() { - let tlsOptions = NIOHTTPClientTLSOptions( + let tlsOptions = SwiftNIOHTTPClientTLSOptions( certificate: "/path/to/cert.pem", privateKey: "/path/to/key.pem" )