diff --git a/.github/actions/checkout-aws-sdk-swift-composite-action/action.yml b/.github/actions/checkout-aws-sdk-swift-composite-action/action.yml new file mode 100644 index 000000000..adf8fc72e --- /dev/null +++ b/.github/actions/checkout-aws-sdk-swift-composite-action/action.yml @@ -0,0 +1,51 @@ +name: 'Checkout aws-sdk-swift composite action' +description: 'A composite action that sets up aws-sdk-swift for the workflow.' +inputs: + AUTOMATION_USER_SSH_PRIVATE_KEY: + description: 'SSH private key for the automation user' + required: true + STAGING_PARTNER_REPO: + description: 'The staging partner repository' + required: true +runs: + using: 'composite' + steps: + - name: Set up SSH key + if: ${{ github.repository != 'smithy-lang/smithy-swift' }} + run: | + mkdir -p ~/.ssh + echo "${{ inputs.AUTOMATION_USER_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan github.com >> ~/.ssh/known_hosts + shell: bash + - name: Select staging aws-sdk-swift branch + if: ${{ github.repository != 'smithy-lang/smithy-swift' }} + run: | + cd smithy-swift + ORIGINAL_REPO_HEAD_REF="$GITHUB_HEAD_REF" \ + DEPENDENCY_REPO_URL="git@github.com:${{ inputs.STAGING_PARTNER_REPO }}.git" \ + ./scripts/ci_steps/select_dependency_branch.sh + shell: bash + - name: Checkout staging aws-sdk-swift + if: ${{ github.repository != 'smithy-lang/smithy-swift' }} + uses: actions/checkout@v4 + with: + repository: ${{ inputs.STAGING_PARTNER_REPO }} + ref: ${{ env.DEPENDENCY_REPO_SHA }} + path: aws-sdk-swift + ssh-key: ${{ inputs.AUTOMATION_USER_SSH_PRIVATE_KEY }} + - name: Select aws-sdk-swift branch + if: ${{ github.repository == 'smithy-lang/smithy-swift' }} + run: | + cd smithy-swift + ORIGINAL_REPO_HEAD_REF="$GITHUB_HEAD_REF" \ + DEPENDENCY_REPO_URL="https://github.com/awslabs/aws-sdk-swift.git" \ + ./scripts/ci_steps/select_dependency_branch.sh + shell: bash + - name: Checkout aws-sdk-swift + if: ${{ github.repository == 'smithy-lang/smithy-swift' }} + uses: actions/checkout@v4 + with: + repository: awslabs/aws-sdk-swift + ref: ${{ env.DEPENDENCY_REPO_SHA }} + path: aws-sdk-swift \ No newline at end of file diff --git a/.github/actions/setup-common-tools-composite-action/action.yml b/.github/actions/setup-common-tools-composite-action/action.yml new file mode 100644 index 000000000..b5998dc1f --- /dev/null +++ b/.github/actions/setup-common-tools-composite-action/action.yml @@ -0,0 +1,30 @@ +name: 'Setup Common Tools' +description: 'A composite action that caches Gradle, caches Swift, and sets up Java.' +runs: + using: 'composite' + steps: + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} + 1-${{ runner.os }}-gradle- + - name: Cache Swift + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/org.swift.swiftpm + ~/.cache/org.swift.swiftpm + key: 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} + restore-keys: | + 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} + 1-${{ runner.os }}-${{ matrix.xcode }}-spm- + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: corretto + java-version: 17 \ No newline at end of file diff --git a/.github/workflows/codegen.yml b/.github/workflows/codegen.yml index 578cb3b7d..fe22c5f20 100644 --- a/.github/workflows/codegen.yml +++ b/.github/workflows/codegen.yml @@ -8,6 +8,7 @@ on: jobs: codegen: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ${{ matrix.os }} name: Java ${{ matrix.java }} ${{ matrix.os }} strategy: diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c58b4dbb4..4f9e575e4 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -11,6 +11,7 @@ env: jobs: apple-ci: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ${{ matrix.runner }} strategy: fail-fast: false @@ -65,31 +66,8 @@ jobs: sudo xcodebuild -runFirstLaunch - name: Checkout smithy-swift uses: actions/checkout@v4 - - name: Cache Gradle - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - 1-${{ runner.os }}-gradle- - - name: Cache Swift - uses: actions/cache@v4 - with: - path: | - ~/Library/Caches/org.swift.swiftpm - ~/.cache/org.swift.swiftpm - key: 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - restore-keys: | - 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - 1-${{ runner.os }}-${{ matrix.xcode }}-spm- - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: corretto - java-version: 17 + - name: Setup common tools + uses: ./.github/actions/setup-common-tools-composite-action - name: Build & Run smithy-swift Kotlin Unit Tests run: ./gradlew build - name: Build & Run smithy-swift Swift Unit Tests @@ -103,6 +81,7 @@ jobs: | xcbeautify apple-downstream: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ${{ matrix.runner }} strategy: fail-fast: false @@ -157,55 +136,24 @@ jobs: sudo xcodebuild -runFirstLaunch - name: Checkout smithy-swift uses: actions/checkout@v4 - - name: Select aws-sdk-swift branch - run: | - ORIGINAL_REPO_HEAD_REF="$GITHUB_HEAD_REF" \ - DEPENDENCY_REPO_URL="https://github.com/awslabs/aws-sdk-swift.git" \ - ./scripts/ci_steps/select_dependency_branch.sh - - name: Checkout aws-sdk-swift - uses: actions/checkout@v4 with: - repository: awslabs/aws-sdk-swift - ref: ${{ env.DEPENDENCY_REPO_SHA }} - path: aws-sdk-swift - - name: Move aws-sdk-swift into place - run: mv aws-sdk-swift .. - - name: Cache Gradle - uses: actions/cache@v4 + path: smithy-swift + - name: Checkout aws-sdk-swift with composite action + uses: ./smithy-swift/.github/actions/checkout-aws-sdk-swift-composite-action with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - 1-${{ runner.os }}-gradle- - - name: Cache Swift - uses: actions/cache@v4 - with: - path: | - ~/Library/Caches/org.swift.swiftpm - ~/.cache/org.swift.swiftpm - key: 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - restore-keys: | - 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - 1-${{ runner.os }}-${{ matrix.xcode }}-spm- - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: corretto - java-version: 17 + AUTOMATION_USER_SSH_PRIVATE_KEY: ${{ secrets.AUTOMATION_USER_SSH_PRIVATE_KEY }} + STAGING_PARTNER_REPO: ${{ secrets.STAGING_PARTNER_REPO }} + - name: Setup common tools + uses: ./smithy-swift/.github/actions/setup-common-tools-composite-action - name: Tools Versions - run: | - cd ../aws-sdk-swift - ./scripts/ci_steps/log_tool_versions.sh + run: ./aws-sdk-swift/scripts/ci_steps/log_tool_versions.sh - name: Prepare aws-sdk-swift Protocol & Unit Tests run: | - cd ../aws-sdk-swift + cd aws-sdk-swift ./scripts/ci_steps/prepare_protocol_and_unit_tests.sh - name: Build and Run aws-sdk-swift Unit Tests run: | - cd ../aws-sdk-swift + cd aws-sdk-swift set -o pipefail && \ NSUnbufferedIO=YES xcodebuild \ -scheme aws-sdk-swift-Package \ @@ -214,7 +162,7 @@ jobs: | xcbeautify - name: Build and Run aws-sdk-swift Protocol Tests run: | - cd ../aws-sdk-swift/codegen + cd aws-sdk-swift/codegen set -o pipefail && \ NSUnbufferedIO=YES xcodebuild \ -scheme aws-sdk-swift-protocol-tests-Package \ @@ -223,6 +171,7 @@ jobs: | xcbeautify linux-ci: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ubuntu-latest strategy: fail-fast: false @@ -235,8 +184,8 @@ jobs: env: ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true steps: - - name: Checkout Sources - uses: actions/checkout@v3 + - name: Checkout smithy-swift + uses: actions/checkout@v4 - name: Install openssl run: | if [ -x "$(command -v apt)" ]; then @@ -244,37 +193,15 @@ jobs: else yum install -y openssl-devel which fi - - name: Cache Gradle - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - 1-${{ runner.os }}-gradle- - - name: Cache Swift - uses: actions/cache@v3 - with: - path: | - ~/Library/Caches/org.swift.swiftpm - ~/.cache/org.swift.swiftpm - key: 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - restore-keys: | - 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - 1-${{ runner.os }}-${{ matrix.xcode }}-spm- - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: corretto - java-version: 17 + - name: Setup common tools + uses: ./.github/actions/setup-common-tools-composite-action - name: Build & Run Kotlin Unit Tests run: ./gradlew build - name: Build & Run Swift Unit Tests run: swift test linux-downstream: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ubuntu-latest strategy: fail-fast: false @@ -287,21 +214,6 @@ jobs: env: ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true steps: - - name: Checkout Sources - uses: actions/checkout@v3 - - name: Select aws-sdk-swift branch - run: | - ORIGINAL_REPO_HEAD_REF="$GITHUB_HEAD_REF" \ - DEPENDENCY_REPO_URL="https://github.com/awslabs/aws-sdk-swift.git" \ - ./scripts/ci_steps/select_dependency_branch.sh - - name: Checkout aws-sdk-swift - uses: actions/checkout@v3 - with: - repository: awslabs/aws-sdk-swift - ref: ${{ env.DEPENDENCY_REPO_SHA }} - path: aws-sdk-swift - - name: Move aws-sdk-swift into place - run: mv aws-sdk-swift .. - name: Install openssl run: | if [ -x "$(command -v apt)" ]; then @@ -309,48 +221,32 @@ jobs: else yum install -y openssl-devel which fi - - name: Cache Gradle - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - 1-${{ runner.os }}-gradle-${{ hashFiles('settings.gradle.kts', 'gradle/wrapper/gradle-wrapper.properties') }} - 1-${{ runner.os }}-gradle- - - name: Cache Swift - uses: actions/cache@v3 + - name: Checkout smithy-swift + uses: actions/checkout@v4 with: - path: | - ~/Library/Caches/org.swift.swiftpm - ~/.cache/org.swift.swiftpm - key: 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - restore-keys: | - 1-${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('Package.swift') }} - 1-${{ runner.os }}-${{ matrix.xcode }}-spm- - - name: Setup Java - uses: actions/setup-java@v3 + path: smithy-swift + - name: Checkout aws-sdk-swift with composite action + uses: ./smithy-swift/.github/actions/checkout-aws-sdk-swift-composite-action with: - distribution: corretto - java-version: 17 + AUTOMATION_USER_SSH_PRIVATE_KEY: ${{ secrets.AUTOMATION_USER_SSH_PRIVATE_KEY }} + STAGING_PARTNER_REPO: ${{ secrets.STAGING_PARTNER_REPO }} + - name: Setup common tools + uses: ./smithy-swift/.github/actions/setup-common-tools-composite-action - name: Tools Versions - run: | - cd ../aws-sdk-swift - ./scripts/ci_steps/log_tool_versions.sh + run: ./aws-sdk-swift/scripts/ci_steps/log_tool_versions.sh - name: Prepare aws-sdk-swift Protocol & Unit Tests run: | - cd ../aws-sdk-swift + cd aws-sdk-swift ./scripts/ci_steps/prepare_protocol_and_unit_tests.sh - name: Build and Run aws-sdk-swift Unit Tests run: | - cd ../aws-sdk-swift + cd aws-sdk-swift swift test - name: Build and Run aws-sdk-swift Protocol Tests run: | export AWS_REGION=us-west-2 export AWS_ACCESS_KEY_ID=test-key-id export AWS_SECRET_ACCESS_KEY=test-secret-access-key - cd ../aws-sdk-swift/codegen + cd aws-sdk-swift/codegen swift test diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8aaa6c8b0..14d1049ca 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,7 @@ on: jobs: ktlint: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - name: Checkout sources @@ -16,6 +17,7 @@ jobs: run: ./gradlew ktlint swiftlint: + if: github.repository == 'smithy-lang/smithy-swift' || github.event_name == 'pull_request' runs-on: ubuntu-latest container: image: ghcr.io/realm/swiftlint:0.54.0 diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 1d2923234..484f7ff57 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -1,22 +1,21 @@ -name: Sync Staging Repo +name: Sync Mirror on: push: - branches: - - main + branches: [ main ] workflow_dispatch: jobs: - sync: + git-sync: + if: github.repository == 'smithy-lang/smithy-swift' runs-on: ubuntu-latest + steps: - - name: Checkout smithy-swift - uses: actions/checkout@v4 + - name: git-sync + uses: wei/git-sync@v3 with: - ref: main - - name: Add staging repo as remote with token - run: | - git remote add staging-repo https://${{ secrets.REPO_SYNC_AUTOMATION_USER_TOKEN }}@github.com/awslabs/private-smithy-swift-staging.git - - name: Push changes to staging repo - run: | - git push staging-repo main + source_repo: ${{ secrets.GIT_SYNC_SOURCE_REPO }} + source_branch: "main" + destination_repo: ${{ secrets.GIT_SYNC_DESTINATION_REPO }} + destination_branch: "main" + destination_ssh_private_key: ${{ secrets.GIT_SYNC_DESTINATION_SSH_PRIVATE_KEY }} \ No newline at end of file diff --git a/Package.version b/Package.version index fe9209eda..557134bcd 100644 --- a/Package.version +++ b/Package.version @@ -1 +1 @@ -0.99.0 \ No newline at end of file +0.106.0 \ No newline at end of file diff --git a/Package.version.next b/Package.version.next index fbd7ac6f8..2500dbc62 100644 --- a/Package.version.next +++ b/Package.version.next @@ -1 +1 @@ -0.100.0 \ No newline at end of file +0.107.0 \ No newline at end of file diff --git a/Sources/ClientRuntime/Networking/Http/CRT/SDKDefaultIO.swift b/Sources/ClientRuntime/Networking/Http/CRT/SDKDefaultIO.swift index b6c38dc62..7ba17e9dd 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/SDKDefaultIO.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/SDKDefaultIO.swift @@ -16,15 +16,11 @@ public final class SDKDefaultIO: @unchecked Sendable { /// Provide singleton access since we want to share and re-use the instance properties public static let shared = SDKDefaultIO() - /// The setter for changing log level of SDKDefaultIO logger. - /// If any log level other than the default log level of `.none` is desired, - /// this setter needs to be called as the first thing in the program. + /// The public setter for setting log level of CRT logger. + /// + /// If any log level other than the default log level of `.none` is desired, this setter **MUST** be called before accessing the `SDKDefaultIO.shared` static field. public static func setLogLevel(level: LogLevel) { - do { - try Logger.initialize(target: .standardOutput, level: level) - } catch { - failOnLogger() - } + SDKDefaultIO.setupLogger(level: level) } private init() { diff --git a/Sources/ClientRuntime/Telemetry/Logging/ClientLogMode.swift b/Sources/ClientRuntime/Telemetry/Logging/ClientLogMode.swift index cc6e19bcd..18f9cf2aa 100644 --- a/Sources/ClientRuntime/Telemetry/Logging/ClientLogMode.swift +++ b/Sources/ClientRuntime/Telemetry/Logging/ClientLogMode.swift @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // +/// Additional logging opt-in for request / response flow. For each selected option other than `.none`, the additional info gets logged at `.debug` level by the `LoggingMiddleware`. public enum ClientLogMode { case none case request diff --git a/Sources/ClientRuntime/Telemetry/Logging/LogLevel+StringExtension.swift b/Sources/ClientRuntime/Telemetry/Logging/LogLevel+StringExtension.swift index b9760cc61..aeefc9879 100644 --- a/Sources/ClientRuntime/Telemetry/Logging/LogLevel+StringExtension.swift +++ b/Sources/ClientRuntime/Telemetry/Logging/LogLevel+StringExtension.swift @@ -5,6 +5,7 @@ import AwsCommonRuntimeKit +/// Extension for CRT's LogLevel enum extension LogLevel { public var stringValue: String { switch self { diff --git a/Sources/ClientRuntime/Telemetry/Logging/SDKLogHandlerFactory.swift b/Sources/ClientRuntime/Telemetry/Logging/SDKLogHandlerFactory.swift index 7f5e677d7..3935b1b68 100644 --- a/Sources/ClientRuntime/Telemetry/Logging/SDKLogHandlerFactory.swift +++ b/Sources/ClientRuntime/Telemetry/Logging/SDKLogHandlerFactory.swift @@ -7,6 +7,7 @@ import Logging +/// Implement this protocol and add an instance of the implementation to `SDKLoggingSystem` to use custom log handlers. public protocol SDKLogHandlerFactory { var label: String { get } func construct(label: String) -> LogHandler diff --git a/Sources/ClientRuntime/Telemetry/Logging/SDKLogLevel.swift b/Sources/ClientRuntime/Telemetry/Logging/SDKLogLevel.swift index 489ef26d1..6c5bf73c4 100644 --- a/Sources/ClientRuntime/Telemetry/Logging/SDKLogLevel.swift +++ b/Sources/ClientRuntime/Telemetry/Logging/SDKLogLevel.swift @@ -7,6 +7,7 @@ import Logging +/// Wrapper for Logger.Level; used by SDKLoggingSystem. public enum SDKLogLevel: String, Codable, CaseIterable { case trace case debug diff --git a/Sources/ClientRuntime/Telemetry/Logging/SDKLoggingSystem.swift b/Sources/ClientRuntime/Telemetry/Logging/SDKLoggingSystem.swift index fb73a1025..d60e17b5f 100644 --- a/Sources/ClientRuntime/Telemetry/Logging/SDKLoggingSystem.swift +++ b/Sources/ClientRuntime/Telemetry/Logging/SDKLoggingSystem.swift @@ -7,22 +7,35 @@ import Logging +/// Use this to turn SDK logging on. public actor SDKLoggingSystem { private var isInitialized = false - private var factories: [String: SDKLogHandlerFactory] = [:] + private var logHandlerFactories: [String: SDKLogHandlerFactory] = [:] public init() {} + /// Adds custom log handler factory to `this.logHandlerFactories`. + /// + /// The added log handler factory will be dedicated log handler for any logger with identical label. public func add(logHandlerFactory: SDKLogHandlerFactory) { - let label = logHandlerFactory.label - factories[label] = logHandlerFactory + logHandlerFactories[logHandlerFactory.label] = logHandlerFactory } + /// Initializes the logging handler factory for the SDK. + /// The default behavior is to log messages at `.error` or more severe levels. + /// + /// The handler factory closure first checks if there exists a custom handler factory in `this.logHandlerFactories` with the same label as the label given to logger initializer. If it exists, then that factory gets used to create the log handler. + /// + /// If no custom handler factory is found for the given label, the factory closure creates and returns a `StreamLogHandler` with minimum log level set to `logLevel`. + /// + /// Loggers output log only if the log level of the message is equal to or more severe than the underlying log handler's log level. E.g., `logger.info(...)` executes only if the underlying log handler's log level is `.info`, `.debug`, or `.trace`. It does not execute if the underlying log handler's minimum log level is any one of the following levels that are more severe than `.info`: `.notice`, `.warning`, `error`, `critical`. + /// + /// - parameters: + /// - logLevel: The minimum log level to use for the log handler if no custom log handler factory was found. Default is `.error`. public func initialize(defaultLogLevel: SDKLogLevel = .error) async { if isInitialized { return } else { isInitialized = true } - let ptr = factories - LoggingSystem.bootstrap { label in - if let factory = ptr[label] { + LoggingSystem.bootstrap { [logHandlerFactories] label in + if let factory = logHandlerFactories[label] { return factory.construct(label: label) } var handler = StreamLogHandler.standardOutput(label: label) @@ -32,11 +45,6 @@ public actor SDKLoggingSystem { } public func initialize(logLevel: SDKLogLevel) async { - if isInitialized { return } else { isInitialized = true } - LoggingSystem.bootstrap { label in - var handler = StreamLogHandler.standardOutput(label: label) - handler.logLevel = logLevel.toLoggerType() - return handler - } + await self.initialize(defaultLogLevel: logLevel) } } diff --git a/Sources/Smithy/Logging/LogAgent.swift b/Sources/Smithy/Logging/LogAgent.swift index 02486ff7b..696e24ad3 100644 --- a/Sources/Smithy/Logging/LogAgent.swift +++ b/Sources/Smithy/Logging/LogAgent.swift @@ -9,9 +9,6 @@ public protocol LogAgent { /// name of the struct or class where the logger was instantiated from var name: String { get } - /// Get or set the configured log level. - var level: LogAgentLevel { get set } - /// This method is called when a `LogAgent` must emit a log message. /// /// - parameters: @@ -31,21 +28,33 @@ public protocol LogAgent { line: UInt) } -public enum LogAgentLevel: String, Codable, CaseIterable { - case trace - case debug - case info - case warn - case error - case fatal -} - +/// Convenience wrapper functions that call `self.log()` with corresponding log level. public extension LogAgent { + /// Use for messages that are typically seen during tracing. + func trace( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.log(level: .trace, + message: message(), + metadata: nil, + source: currentModule(fileID: file), + file: file, + function: function, + line: line) + } - /// Log a message passing with the `.info` log level. - func info(_ message: String, file: String = #fileID, function: String = #function, line: UInt = #line) { - self.log(level: .info, - message: message, + /// Use for messages that are typically seen during debugging. + func debug( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.log(level: .debug, + message: message(), metadata: nil, source: currentModule(fileID: file), file: file, @@ -53,10 +62,15 @@ public extension LogAgent { line: line) } - /// Log a message passing with the `LogLevel.warn` log level. - func warn(_ message: String, file: String = #fileID, function: String = #function, line: UInt = #line) { - self.log(level: .warn, - message: message, + /// Use for informational messages. + func info( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.log(level: .info, + message: message(), metadata: nil, source: currentModule(fileID: file), file: file, @@ -64,10 +78,15 @@ public extension LogAgent { line: line) } - /// Log a message passing with the `.debug` log level. - func debug(_ message: String, file: String = #fileID, function: String = #function, line: UInt = #line) { - self.log(level: .debug, - message: message, + /// Use for non-error messages that may need special attention. + func notice( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.log(level: .notice, + message: message(), metadata: nil, source: currentModule(fileID: file), file: file, @@ -75,10 +94,15 @@ public extension LogAgent { line: line) } - /// Log a message passing with the `.error` log level. - func error(_ message: String, file: String = #fileID, function: String = #function, line: UInt = #line) { - self.log(level: .error, - message: message, + /// Use for non-error messages that are more severe than `.notice`. + func warn( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.log(level: .warn, + message: message(), metadata: nil, source: currentModule(fileID: file), file: file, @@ -86,10 +110,15 @@ public extension LogAgent { line: line) } - /// Log a message passing with the `.trace` log level. - func trace(_ message: String, file: String = #fileID, function: String = #function, line: UInt = #line) { - self.log(level: .trace, - message: message, + /// Use for errors. + func error( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + self.log(level: .error, + message: message(), metadata: nil, source: currentModule(fileID: file), file: file, @@ -97,10 +126,16 @@ public extension LogAgent { line: line) } - /// Log a message passing with the `.fatal` log level. - func fatal(_ message: String, file: String = #fileID, function: String = #function, line: UInt = #line) { + /// Appropriate for critical error conditions that usually require immediate + /// attention. + func fatal( + _ message: @autoclosure() -> String, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { self.log(level: .fatal, - message: message, + message: message(), metadata: nil, source: currentModule(fileID: file), file: file, diff --git a/Sources/Smithy/Logging/LogAgentLevel.swift b/Sources/Smithy/Logging/LogAgentLevel.swift new file mode 100644 index 000000000..85a76e903 --- /dev/null +++ b/Sources/Smithy/Logging/LogAgentLevel.swift @@ -0,0 +1,38 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Logging + +/// Wrapper for Logger.Level; used by LogAgent and SwiftLogger. +public enum LogAgentLevel: String, Codable, CaseIterable { + case trace + case debug + case info + case notice + case warn + case error + case fatal + + func toLoggerLevel() -> Logger.Level { + switch self { + case .trace: + return .trace + case .debug: + return .debug + case .info: + return .info + case .notice: + return .notice + case .warn: + return .warning + case .error: + return .error + case .fatal: + return .critical + } + } +} diff --git a/Sources/Smithy/Logging/SwiftLog+LogAgent.swift b/Sources/Smithy/Logging/SwiftLog+LogAgent.swift index 83cc2ff1c..f99abacba 100644 --- a/Sources/Smithy/Logging/SwiftLog+LogAgent.swift +++ b/Sources/Smithy/Logging/SwiftLog+LogAgent.swift @@ -8,27 +8,22 @@ import Logging public struct SwiftLogger: LogAgent { - public var level: LogAgentLevel - private let logger: Logger private let label: String public init(label: String) { self.label = label self.logger = Logger(label: label) - self.level = LogAgentLevel.info } + @available(*, deprecated, message: "This API is deprecated. Use init(label:) instead.") public init(label: String, logLevel: LogAgentLevel) { - self.label = label - self.logger = Logger(label: label) - self.level = logLevel + self.init(label: label) } // This initializer is currently only used in tests, to inject a mock LogHandler. - init(label: String, logLevel: LogAgentLevel, factory: (String) -> any LogHandler) { + init(label: String, factory: (String) -> any LogHandler) { self.label = label - self.level = logLevel self.logger = Logger(label: label, factory: factory) } @@ -56,23 +51,3 @@ public struct SwiftLogger: LogAgent { ) } } - -extension LogAgentLevel { - - func toLoggerLevel() -> Logger.Level { - switch self { - case .trace: - return .trace - case .debug: - return .debug - case .info: - return .info - case .warn: - return .warning - case .error: - return .error - case .fatal: - return .critical - } - } -} diff --git a/Sources/SmithyHTTPAPI/HTTPResponse.swift b/Sources/SmithyHTTPAPI/HTTPResponse.swift index 0840d9990..20e430c1e 100644 --- a/Sources/SmithyHTTPAPI/HTTPResponse.swift +++ b/Sources/SmithyHTTPAPI/HTTPResponse.swift @@ -8,43 +8,48 @@ import protocol Smithy.ResponseMessage import protocol Smithy.Stream import enum Smithy.ByteStream -import class Foundation.DispatchQueue +import class Foundation.NSRecursiveLock -public class HTTPResponse: HTTPURLResponse, ResponseMessage { +public final class HTTPResponse: ResponseMessage, @unchecked Sendable { + private var lock = NSRecursiveLock() - public var headers: Headers - public var body: ByteStream - public var reason: String? + private var _headers: Headers + public var headers: Headers { + get { lock.lock(); defer { lock.unlock() }; return _headers } + set { lock.lock(); defer { lock.unlock() }; self._headers = newValue } + } + + private var _body: ByteStream + public var body: ByteStream { + get { lock.lock(); defer { lock.unlock() }; return _body } + set { lock.lock(); defer { lock.unlock() }; self._body = newValue } + } private var _statusCode: HTTPStatusCode - private let statusCodeQueue = DispatchQueue(label: "statusCodeSerialQueue") public var statusCode: HTTPStatusCode { - get { - statusCodeQueue.sync { - return _statusCode - } - } - set { - statusCodeQueue.sync { - self._statusCode = newValue - } - } + get { lock.lock(); defer { lock.unlock() }; return _statusCode } + set { lock.lock(); defer { lock.unlock() }; self._statusCode = newValue } } + public let reason: String? + public init( headers: Headers = .init(), statusCode: HTTPStatusCode = .processing, body: ByteStream = .noStream, - reason: String? = nil) { - self.headers = headers + reason: String? = nil + ) { + self._headers = headers self._statusCode = statusCode - self.body = body + self._body = body + self.reason = reason } public init(headers: Headers = .init(), body: ByteStream, statusCode: HTTPStatusCode, reason: String? = nil) { - self.body = body + self._body = body self._statusCode = statusCode - self.headers = headers + self._headers = headers + self.reason = reason } /** diff --git a/Sources/SmithyHTTPAPI/HTTPURLResponse.swift b/Sources/SmithyHTTPAPI/HTTPURLResponse.swift deleted file mode 100644 index 3498db019..000000000 --- a/Sources/SmithyHTTPAPI/HTTPURLResponse.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import enum Smithy.ByteStream - -protocol HTTPURLResponse { - var headers: Headers { get set } - var body: ByteStream { get set } - var statusCode: HTTPStatusCode { get set } - var reason: String? { get set } -} diff --git a/Sources/SmithyHTTPAPI/Headers.swift b/Sources/SmithyHTTPAPI/Headers.swift index 6358ecc6f..bf9100359 100644 --- a/Sources/SmithyHTTPAPI/Headers.swift +++ b/Sources/SmithyHTTPAPI/Headers.swift @@ -5,8 +5,28 @@ // SPDX-License-Identifier: Apache-2.0 // -public struct Headers: Sendable { - public var headers: [Header] = [] +import class Foundation.NSRecursiveLock + +public struct Headers: @unchecked Sendable { + private let lock = NSRecursiveLock() + + private var _headers: [Header] = [] + public var headers: [Header] { + get { access { $0 } } + set { mutate { $0 = newValue } } + } + + private mutating func mutate(_ block: (inout [Header]) -> Void) { + lock.lock() + defer { lock.unlock() } + block(&_headers) + } + + private func access(_ block: ([Header]) throws -> T) rethrows -> T { + lock.lock() + defer { lock.unlock() } + return try block(_headers) + } /// Creates an empty instance. public init() {} @@ -53,11 +73,13 @@ public struct Headers: Sendable { /// - Parameters: /// - header: The `Header` to be added or updated. public mutating func add(_ header: Header) { - guard let index = headers.index(of: header.name) else { - headers.append(header) - return + mutate { headers in + guard let index = headers.index(of: header.name) else { + headers.append(header) + return + } + headers[index].value.append(contentsOf: header.value) } - headers[index].value.append(contentsOf: header.value) } /// Case-insensitively updates the value of a `Header` by replacing the values of it or appends a `Header` @@ -66,11 +88,13 @@ public struct Headers: Sendable { /// - Parameters: /// - header: The `Header` to be added or updated. public mutating func update(_ header: Header) { - guard let index = headers.index(of: header.name) else { - headers.append(header) - return + mutate { headers in + guard let index = headers.index(of: header.name) else { + headers.append(header) + return + } + headers.replaceSubrange(index...index, with: [header]) } - headers.replaceSubrange(index...index, with: [header]) } /// Case-insensitively updates the value of a `Header` by replacing the values of it or appends a `Header` @@ -97,17 +121,20 @@ public struct Headers: Sendable { /// /// - Parameters: /// - headers: The `Headers` object. - public mutating func addAll(headers: Headers) { - self.headers.append(contentsOf: headers.headers) + public mutating func addAll(headers otherHeaders: Headers) { + mutate { headers in + headers.append(contentsOf: otherHeaders.headers) + } } /// Case-insensitively removes a `Header`, if it exists, from the instance. /// /// - Parameter name: The name of the `HTTPHeader` to remove. public mutating func remove(name: String) { - guard let index = headers.index(of: name) else { return } - - headers.remove(at: index) + mutate { headers in + guard let index = headers.index(of: name) else { return } + headers.remove(at: index) + } } /// Case-insensitively find a header's values by name. @@ -116,13 +143,14 @@ public struct Headers: Sendable { /// /// - Returns: The values of the header, if they exist. public func values(for name: String) -> [String]? { - guard let indices = headers.indices(of: name), !indices.isEmpty else { return nil } - var values = [String]() - for index in indices { - values.append(contentsOf: headers[index].value) + access { headers in + guard let indices = headers.indices(of: name), !indices.isEmpty else { return nil } + var values = [String]() + for index in indices { + values.append(contentsOf: headers[index].value) + } + return values } - - return values } /// Case-insensitively find a header's value by name. @@ -131,29 +159,28 @@ public struct Headers: Sendable { /// /// - Returns: The value of header as a comma delimited string, if it exists. public func value(for name: String) -> String? { - guard let values = values(for: name) else { - return nil - } + guard let values = values(for: name) else { return nil } return values.joined(separator: ",") } public func exists(name: String) -> Bool { - headers.index(of: name) != nil + access { $0.index(of: name) != nil } } /// The dictionary representation of all headers. /// /// This representation does not preserve the current order of the instance. public var dictionary: [String: [String]] { - let namesAndValues = headers.map { ($0.name, $0.value) } - - return Dictionary(namesAndValues) { (first, last) -> [String] in - return first + last + access { headers in + let namesAndValues = headers.map { ($0.name, $0.value) } + return Dictionary(namesAndValues) { (first, last) -> [String] in + first + last + } } } public var isEmpty: Bool { - return self.headers.isEmpty + access { $0.isEmpty } } } @@ -164,14 +191,18 @@ extension Headers: Equatable { /// - rhs: The second `Headers` to compare. /// - Returns: `true` if the two values are equal irrespective of order, otherwise `false`. public static func == (lhs: Headers, rhs: Headers) -> Bool { - return lhs.headers.sorted() == rhs.headers.sorted() + lhs.access { lhsHeaders in + rhs.access { rhsHeaders in + lhsHeaders.sorted() == rhsHeaders.sorted() + } + } } } extension Headers: Hashable { public func hash(into hasher: inout Hasher) { - hasher.combine(headers.sorted()) + access { hasher.combine($0.sorted()) } } } diff --git a/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift b/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift index 02b167d88..a9b3ae478 100644 --- a/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift +++ b/Tests/ClientRuntimeTests/LoggingTests/SwiftLoggerTests.swift @@ -38,7 +38,7 @@ final class SwiftLoggerTests: XCTestCase { private func logsLeveledMessage( logLevel: Logger.Level, - loggerBlock: (SwiftLogger) -> (String, String, String, UInt) -> Void, + loggerBlock: (SwiftLogger) -> (@autoclosure() -> String, String, String, UInt) -> Void, testFile: StaticString = #filePath, testLine: UInt = #line ) throws { @@ -53,7 +53,7 @@ final class SwiftLoggerTests: XCTestCase { // Create a TestLogHandler, then create a SwiftLogger (the test subject) // with it. var logHandler: TestLogHandler! - let subject = SwiftLogger(label: "Test", logLevel: .trace, factory: { label in + let subject = SwiftLogger(label: "Test", factory: { label in logHandler = TestLogHandler(label: label) return logHandler }) diff --git a/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift b/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift index 7bf46b480..d86f84bf3 100644 --- a/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift +++ b/Tests/ClientRuntimeTests/NetworkingTests/URLSession/FoundationStreamBridgeTests.swift @@ -109,12 +109,9 @@ private class TestLogger: LogAgent { var messages: [(level: LogAgentLevel, message: String)] = [] - var level: LogAgentLevel - init(name: String = "Test", messages: [(level: LogAgentLevel, message: String)] = [], level: LogAgentLevel = .info) { self.name = name self.messages = messages - self.level = level } func log(level: LogAgentLevel = .info, message: @autoclosure () -> String, metadata: @autoclosure () -> [String : String]? = nil, source: @autoclosure () -> String = "ChecksumUnitTests", file: String = #file, function: String = #function, line: UInt = #line) { diff --git a/gradle.properties b/gradle.properties index 0fddf21da..d6f7479d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.code.style=official # config # codegen -smithyVersion=1.52.1 +smithyVersion=1.53.0 smithyGradleVersion=0.6.0 # kotlin diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt index 673da8e55..e5b7ec7ed 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt @@ -28,6 +28,7 @@ import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.toMemberNames +import software.amazon.smithy.swift.codegen.swiftmodules.FoundationTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -160,11 +161,12 @@ class ShapeValueGenerator( } ShapeType.BLOB -> { if (shape.hasTrait()) { - writer.writeInline(".stream(\$N(data: ", SmithyStreamsTypes.Core.BufferedStream) - ".data(using: .utf8)!, isClosed: true))" + writer.writeInline(".stream(\$N(data: \$N(", SmithyStreamsTypes.Core.BufferedStream, FoundationTypes.Data) + ".utf8), isClosed: true))" } else { // TODO: properly handle this optional with an unwrapped statement before it's passed as a value to a shape. - ".data(using: .utf8)!" + writer.writeInline("\$N(", FoundationTypes.Data) + ".utf8)" } } else -> { "" } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt index d11d00fd2..88dcd287b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt @@ -215,10 +215,11 @@ class StructureGenerator( writer.writeAvailableAttribute(model, shape) writer.openBlock( - "public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N {", + "public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N {", ClientRuntimeTypes.Core.ModeledError, ClientRuntimeTypes.Http.HttpError, - SwiftTypes.Error + SwiftTypes.Error, + SwiftTypes.Protocols.Sendable, ) .call { generateErrorStructMembers() } .write("") @@ -234,7 +235,7 @@ class StructureGenerator( private fun generateErrorStructMembers() { if (membersSortedByName.isNotEmpty()) { writer.write("") - writer.openBlock("public struct Properties {", "}") { + writer.openBlock("public struct Properties: \$N {", "}", SwiftTypes.Protocols.Sendable) { membersSortedByName.forEach { val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(it) { return@forEach } writer.writeMemberDocs(model, it) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt index a1234bd82..4942e639b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt @@ -380,14 +380,14 @@ class SwiftSymbolProvider(private val model: Model, val swiftSettings: SwiftSett if (shape.hasTrait()) { { writer -> writer.format( - "\$N.data(\$N(\"$literal\".utf8))", + "\$N.data(\$N(base64Encoded: \"$literal\"))", SmithyTypes.ByteStream, FoundationTypes.Data ) } } else { { writer -> - writer.format("\$N(\"$literal\".utf8)", FoundationTypes.Data) + writer.format("\$N(base64Encoded: \"$literal\")", FoundationTypes.Data) } } ) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index 510c1cd6b..e0bbde818 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -243,14 +243,16 @@ open class MemberShapeDecodeGenerator( writer.addImport(FoundationTypes.Data) return if (targetShape.hasTrait()) { writer.format( - " ?? \$N.data(\$N(\"$value\".utf8))", + " ?? \$N.data(\$N(base64Encoded: \$S))", SmithyTypes.ByteStream, - FoundationTypes.Data + FoundationTypes.Data, + value, ) } else { writer.format( - " ?? \$N(\"$value\".utf8)", - FoundationTypes.Data + " ?? \$N(base64Encoded: \$S)", + FoundationTypes.Data, + value, ) } } diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructureGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructureGeneratorTests.kt index 536949b6b..8a21ba2f7 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructureGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructureGeneratorTests.kt @@ -262,9 +262,9 @@ public struct RecursiveShapesInputOutputLists: Swift.Sendable { contents.shouldContain(swiftSettings.copyrightNotice) val expectedGeneratedStructure = """ -public struct MyError: ClientRuntime.ModeledError, ClientRuntime.ServiceError, ClientRuntime.HTTPError, Swift.Error { +public struct MyError: ClientRuntime.ModeledError, ClientRuntime.ServiceError, ClientRuntime.HTTPError, Swift.Error, Swift.Sendable { - public struct Properties { + public struct Properties: Swift.Sendable { /// This is documentation about the member. public internal(set) var baz: Swift.Int? = nil public internal(set) var message: Swift.String? = nil