diff --git a/.editorconfig b/.editorconfig
index db30a6257e8..55bfe517da2 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,5 +9,11 @@ indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
+
# Ktlint-specific config
-disabled_rules = filename, max-line-length, argument-list-wrapping, parameter-list-wrapping
+ktlint_standard = enabled
+ktlint_experimental = disabled
+ktlint_standard_filename = disabled
+ktlint_standard_max-line-length = disabled
+ktlint_standard_argument-list-wrapping = disabled
+ktlint_standard_parameter-list-wrapping = disabled
diff --git a/.github/scripts/docker-image-hash b/.github/scripts/docker-image-hash
index 55b532332df..71fae16318d 100755
--- a/.github/scripts/docker-image-hash
+++ b/.github/scripts/docker-image-hash
@@ -11,4 +11,4 @@ set -eo pipefail
cd "$(dirname "$0")"
cd "$(git rev-parse --show-toplevel)"
-git ls-files -s --full-name "tools" | git hash-object --stdin
+git ls-files -s --full-name "tools/ci-build" | git hash-object --stdin
diff --git a/.github/scripts/get-or-create-release-branch.sh b/.github/scripts/get-or-create-release-branch.sh
index 6cdc8299111..7dcc87676d0 100755
--- a/.github/scripts/get-or-create-release-branch.sh
+++ b/.github/scripts/get-or-create-release-branch.sh
@@ -8,8 +8,6 @@ set -eux
# Compute the name of the release branch starting from the version that needs to be released ($SEMANTIC_VERSION).
# If it's the beginning of a new release series, the branch is created and pushed to the remote (chosen according to
# the value $DRY_RUN).
-# If it isn't the beginning of a new release series, the script makes sure that the commit that will be tagged is at
-# the tip of the (pre-existing) release branch.
#
# The script populates an output file with key-value pairs that are needed in the release CI workflow to carry out
# the next steps in the release flow: the name of the release branch and a boolean flag that is set to 'true' if this
@@ -57,16 +55,7 @@ if [[ "${DRY_RUN}" == "true" ]]; then
git push --force origin "HEAD:refs/heads/${branch_name}"
else
commit_sha=$(git rev-parse --short HEAD)
- if git ls-remote --exit-code --heads origin "${branch_name}"; then
- # The release branch already exists, we need to make sure that our commit is its current tip
- branch_head_sha=$(git rev-parse --verify --short "refs/heads/${branch_name}")
- if [[ "${branch_head_sha}" != "${commit_sha}" ]]; then
- echo "The release branch - ${branch_name} - already exists. ${commit_sha}, the commit you chose when "
- echo "launching this release, is not its current HEAD (${branch_head_sha}). This is not allowed: you "
- echo "MUST release from the HEAD of the release branch if it already exists."
- exit 1
- fi
- else
+ if ! git ls-remote --exit-code --heads origin "${branch_name}"; then
# The release branch does not exist.
# We need to make sure that the commit SHA that we are releasing is on `main`.
git fetch origin main
@@ -75,7 +64,7 @@ else
git checkout -b "${branch_name}"
git push origin "${branch_name}"
else
- echo "You must choose a commit from main to create a new release series!"
+ echo "You must choose a commit from main to create a new release branch!"
exit 1
fi
fi
diff --git a/.github/workflows/ci-merge-queue.yml b/.github/workflows/ci-merge-queue.yml
new file mode 100644
index 00000000000..bedab9beb5a
--- /dev/null
+++ b/.github/workflows/ci-merge-queue.yml
@@ -0,0 +1,93 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+# This workflow runs CI for the GitHub merge queue.
+
+name: Merge Queue CI
+on:
+ merge_group:
+ types: [checks_requested]
+
+# Allow one instance of this workflow per merge
+concurrency:
+ group: ci-merge-queue-yml-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ ecr_repository: public.ecr.aws/w0m4q9l7/github-awslabs-smithy-rs-ci
+
+jobs:
+ # This job will, if possible, save a docker login password to the job outputs. The token will
+ # be encrypted with the passphrase stored as a GitHub secret. The login password expires after 12h.
+ # The login password is encrypted with the repo secret DOCKER_LOGIN_TOKEN_PASSPHRASE
+ save-docker-login-token:
+ name: Save a docker login token
+ outputs:
+ docker-login-password: ${{ steps.set-token.outputs.docker-login-password }}
+ permissions:
+ id-token: write
+ contents: read
+ continue-on-error: true
+ runs-on: ubuntu-latest
+ steps:
+ - name: Attempt to load a docker login password
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.SMITHY_RS_PUBLIC_ECR_PUSH_ROLE_ARN }}
+ role-session-name: GitHubActions
+ aws-region: us-west-2
+ - name: Save the docker login password to the output
+ id: set-token
+ run: |
+ ENCRYPTED_PAYLOAD=$(
+ gpg --symmetric --batch --passphrase "${{ secrets.DOCKER_LOGIN_TOKEN_PASSPHRASE }}" --output - <(aws ecr-public get-login-password --region us-east-1) | base64 -w0
+ )
+ echo "docker-login-password=$ENCRYPTED_PAYLOAD" >> $GITHUB_OUTPUT
+
+ # This job detects if the PR made changes to build tools. If it did, then it builds a new
+ # build Docker image. Otherwise, it downloads a build image from Public ECR. In both cases,
+ # it uploads the image as a build artifact for other jobs to download and use.
+ acquire-base-image:
+ name: Acquire Base Image
+ needs: save-docker-login-token
+ runs-on: ubuntu-latest
+ env:
+ ENCRYPTED_DOCKER_PASSWORD: ${{ needs.save-docker-login-token.outputs.docker-login-password }}
+ DOCKER_LOGIN_TOKEN_PASSPHRASE: ${{ secrets.DOCKER_LOGIN_TOKEN_PASSPHRASE }}
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ path: smithy-rs
+ - name: Acquire base image
+ id: acquire
+ env:
+ DOCKER_BUILDKIT: 1
+ run: ./smithy-rs/.github/scripts/acquire-build-image
+ - name: Acquire credentials
+ uses: aws-actions/configure-aws-credentials@v1-node16
+ with:
+ role-to-assume: ${{ secrets.SMITHY_RS_PUBLIC_ECR_PUSH_ROLE_ARN }}
+ role-session-name: GitHubActions
+ aws-region: us-west-2
+ - name: Upload image
+ run: |
+ IMAGE_TAG="$(./smithy-rs/.github/scripts/docker-image-hash)"
+ docker tag "smithy-rs-base-image:${IMAGE_TAG}" "${{ env.ecr_repository }}:${IMAGE_TAG}"
+ aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
+ docker push "${{ env.ecr_repository }}:${IMAGE_TAG}"
+
+ # Run shared CI after the Docker build image has either been rebuilt or found in ECR
+ ci:
+ needs:
+ - save-docker-login-token
+ - acquire-base-image
+ if: ${{ github.event.pull_request.head.repo.full_name == 'awslabs/smithy-rs' || toJSON(github.event.merge_group) != '{}' }}
+ uses: ./.github/workflows/ci.yml
+ with:
+ run_sdk_examples: true
+ secrets:
+ ENCRYPTED_DOCKER_PASSWORD: ${{ needs.save-docker-login-token.outputs.docker-login-password }}
+ DOCKER_LOGIN_TOKEN_PASSPHRASE: ${{ secrets.DOCKER_LOGIN_TOKEN_PASSPHRASE }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 774c4f3c7a3..52b54efd45e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,7 +29,7 @@ on:
required: false
env:
- rust_version: 1.62.1
+ rust_version: 1.63.0
rust_toolchain_components: clippy,rustfmt
ENCRYPTED_DOCKER_PASSWORD: ${{ secrets.ENCRYPTED_DOCKER_PASSWORD }}
DOCKER_LOGIN_TOKEN_PASSPHRASE: ${{ secrets.DOCKER_LOGIN_TOKEN_PASSPHRASE }}
diff --git a/.github/workflows/claim-crate-names.yml b/.github/workflows/claim-crate-names.yml
index 650b8cac7cd..63b5eac3079 100644
--- a/.github/workflows/claim-crate-names.yml
+++ b/.github/workflows/claim-crate-names.yml
@@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true
env:
- rust_version: 1.62.1
+ rust_version: 1.63.0
name: Claim unpublished crate names on crates.io
run-name: ${{ github.workflow }}
diff --git a/.github/workflows/pull-request-bot.yml b/.github/workflows/pull-request-bot.yml
index 7bcd2e833f8..131939fcb0d 100644
--- a/.github/workflows/pull-request-bot.yml
+++ b/.github/workflows/pull-request-bot.yml
@@ -28,7 +28,7 @@ concurrency:
env:
java_version: 11
- rust_version: 1.62.1
+ rust_version: 1.63.0
rust_toolchain_components: clippy,rustfmt
apt_dependencies: libssl-dev gnuplot jq
diff --git a/.github/workflows/release-scripts/create-release.js b/.github/workflows/release-scripts/create-release.js
index ad4f328e8f4..fb10ea1b2c0 100644
--- a/.github/workflows/release-scripts/create-release.js
+++ b/.github/workflows/release-scripts/create-release.js
@@ -44,10 +44,13 @@ module.exports = async ({
isDryRun,
// Release manifest file path
releaseManifestPath,
+ // The commit-like reference that we want to release (e.g. a commit SHA or a branch name)
+ releaseCommitish,
}) => {
assert(github !== undefined, "The `github` argument is required");
assert(isDryRun !== undefined, "The `isDryRun` argument is required");
assert(releaseManifestPath !== undefined, "The `releaseManifestPath` argument is required");
+ assert(releaseCommitish !== undefined, "The `releaseCommitish` argument is required");
console.info(`Starting GitHub release creation with isDryRun: ${isDryRun}, and releaseManifestPath: '${releaseManifestPath}'`);
@@ -74,6 +77,7 @@ module.exports = async ({
name: releaseManifest.name,
body: releaseManifest.body,
prerelease: releaseManifest.prerelease,
+ target_commitish: releaseCommitish,
});
console.info(`SUCCESS: Created release with ID: ${response.data.id}, URL: ${response.data.html_url} `);
} else {
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 580eb26245a..bcc1cf22ab6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true
env:
- rust_version: 1.62.1
+ rust_version: 1.63.0
name: Release smithy-rs
run-name: ${{ github.workflow }} ${{ inputs.semantic_version }} (${{ inputs.commit_sha }}) - ${{ inputs.dry_run && 'Dry run' || 'Production run' }}
@@ -18,8 +18,8 @@ on:
workflow_dispatch:
inputs:
commit_sha:
- description: |
- The SHA of the git commit that you want to release.
+ description: |
+ The SHA of the git commit that you want to release.
You must use the non-abbreviated SHA (e.g. b2318b0 won't work!).
required: true
type: string
@@ -75,8 +75,8 @@ jobs:
# We need `always` here otherwise this job won't run if the previous job has been skipped
# See https://samanpavel.medium.com/github-actions-conditional-job-execution-e6aa363d2867
if: |
- always() &&
- needs.acquire-base-image.result == 'success' &&
+ always() &&
+ needs.acquire-base-image.result == 'success' &&
(needs.release-ci.result == 'success' || needs.release-ci.result == 'skipped')
runs-on: ubuntu-latest
outputs:
@@ -87,6 +87,7 @@ jobs:
with:
ref: ${{ inputs.commit_sha }}
token: ${{ secrets.RELEASE_AUTOMATION_BOT_PAT }}
+ fetch-depth: 0
- name: Get or create release branch
id: branch-push
shell: bash
@@ -112,11 +113,13 @@ jobs:
runs-on: ubuntu-latest
outputs:
release_branch: ${{ needs.get-or-create-release-branch.outputs.release_branch }}
+ commit_sha: ${{ steps.gradle-push.outputs.commit_sha }}
steps:
- uses: actions/checkout@v3
with:
- ref: ${{ needs.get-or-create-release-branch.outputs.release_branch }}
+ ref: ${{ inputs.commit_sha }}
path: smithy-rs
+ fetch-depth: 0
token: ${{ secrets.RELEASE_AUTOMATION_BOT_PAT }}
- name: Upgrade gradle.properties
uses: ./smithy-rs/.github/actions/docker-build
@@ -131,13 +134,30 @@ jobs:
shell: bash
env:
SEMANTIC_VERSION: ${{ inputs.semantic_version }}
+ RELEASE_COMMIT_SHA: ${{ inputs.commit_sha }}
+ RELEASE_BRANCH_NAME: ${{ needs.get-or-create-release-branch.outputs.release_branch }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -x
+
# For debugging purposes
git status
- # The file was actually changed, we need to commit the changes
- git diff-index --quiet HEAD || { git -c 'user.name=AWS SDK Rust Bot' -c 'user.email=aws-sdk-rust-primary@amazon.com' commit gradle.properties --message "Upgrade the smithy-rs runtime crates version to ${SEMANTIC_VERSION}" && git push origin; }
+
+ if ! git diff-index --quiet HEAD; then
+ # gradle.properties was changed, we need to commit and push the diff
+ git -c 'user.name=AWS SDK Rust Bot' -c 'user.email=aws-sdk-rust-primary@amazon.com' commit gradle.properties --message "Upgrade the smithy-rs runtime crates version to ${SEMANTIC_VERSION}"
+
+ # This will fail if we tried to release from a non-HEAD commit on the release branch.
+ # The only scenario where we would try to release a non-HEAD commit from the release branch is
+ # to retry a release action execution that failed due to a transient issue.
+ # In that case, we expect the commit to be releasable as-is, i.e. the runtime crate version in gradle.properties
+ # should already be the expected one.
+ git push origin "HEAD:refs/heads/${RELEASE_BRANCH_NAME}"
+
+ echo "commit_sha=$(git rev-parse HEAD)" > $GITHUB_OUTPUT
+ else
+ echo "commit_sha=${RELEASE_COMMIT_SHA}" > $GITHUB_OUTPUT
+ fi
release:
name: Release
@@ -158,7 +178,7 @@ jobs:
- name: Checkout smithy-rs
uses: actions/checkout@v3
with:
- ref: ${{ needs.upgrade-gradle-properties.outputs.release_branch }}
+ ref: ${{ needs.upgrade-gradle-properties.outputs.commit_sha }}
path: smithy-rs
token: ${{ secrets.RELEASE_AUTOMATION_BOT_PAT }}
- name: Generate release artifacts
@@ -170,9 +190,20 @@ jobs:
- name: Push smithy-rs changes
shell: bash
working-directory: smithy-rs-release/smithy-rs
+ id: push-changelog
+ env:
+ RELEASE_BRANCH_NAME: ${{ needs.upgrade-gradle-properties.outputs.release_branch }}
run: |
- echo "Pushing release commits..."
- git push origin
+ if ! git diff-index --quiet HEAD; then
+ echo "Pushing release commits..."
+ # This will fail if we tried to release from a non-HEAD commit on the release branch.
+ # The only scenario where we would try to release a non-HEAD commit from the release branch is
+ # to retry a release action execution that failed due to a transient issue.
+ # In that case, we expect the commit to be releasable as-is, i.e. the changelog should have already
+ # been processed.
+ git push origin "HEAD:refs/heads/${RELEASE_BRANCH_NAME}"
+ fi
+ echo "commit_sha=$(git rev-parse HEAD)" > $GITHUB_OUTPUT
- name: Tag release
uses: actions/github-script@v6
with:
@@ -182,7 +213,8 @@ jobs:
await createReleaseScript({
github,
isDryRun: ${{ inputs.dry_run }},
- releaseManifestPath: "smithy-rs-release/smithy-rs-release-manifest.json"
+ releaseManifestPath: "smithy-rs-release/smithy-rs-release-manifest.json",
+ releaseCommitish: "${{ steps.push-changelog.outputs.commit_sha }}"
});
- name: Publish to crates.io
shell: bash
@@ -232,7 +264,7 @@ jobs:
shell: bash
run: |
set -eux
-
+
# This will fail if other commits have been pushed to `main` after `commit_sha`
# In particular, this will ALWAYS fail if you are creating a new release series from
# a commit that is not the current tip of `main`.
diff --git a/.github/workflows/update-sdk-next.yml b/.github/workflows/update-sdk-next.yml
index 8fca2bbc62c..fd6082351e3 100644
--- a/.github/workflows/update-sdk-next.yml
+++ b/.github/workflows/update-sdk-next.yml
@@ -32,7 +32,7 @@ jobs:
- name: Set up Rust
uses: dtolnay/rust-toolchain@master
with:
- toolchain: 1.62.1
+ toolchain: 1.63.0
- name: Delete old SDK
run: |
- name: Generate a fresh SDK
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c6da8695fcd..cce6e263271 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,10 +20,10 @@ repos:
files: ^.*$
pass_filenames: false
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
- rev: v2.5.0
+ rev: v2.6.0
hooks:
- id: pretty-format-kotlin
- args: [--autofix, --ktlint-version, 0.46.1]
+ args: [--autofix, --ktlint-version, 0.48.2]
- id: pretty-format-yaml
args: [--autofix, --indent, '2']
- id: pretty-format-rust
diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml
index 7a5f6d7515a..5317cd5c7b1 100644
--- a/CHANGELOG.next.toml
+++ b/CHANGELOG.next.toml
@@ -11,120 +11,231 @@
# meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"}
# author = "rcoh"
+[[aws-sdk-rust]]
+message = """Request IDs can now be easily retrieved on successful responses. For example, with S3:
+```rust
+// Import the trait to get the `request_id` method on outputs
+use aws_sdk_s3::types::RequestId;
+let output = client.list_buckets().send().await?;
+println!("Request ID: {:?}", output.request_id());
+```
+"""
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false }
+author = "jdisanti"
+
+[[aws-sdk-rust]]
+message = """Retrieving a request ID from errors now requires importing the `RequestId` trait. For example, with S3:
+```rust
+use aws_sdk_s3::types::RequestId;
+println!("Request ID: {:?}", error.request_id());
+```
+"""
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false }
+author = "jdisanti"
+
+[[smithy-rs]]
+message = "Generic clients no longer expose a `request_id()` function on errors. To get request ID functionality, use the SDK code generator."
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"}
+author = "jdisanti"
+
+[[aws-sdk-rust]]
+message = "The `message()` and `code()` methods on errors have been moved into `ProvideErrorMetadata` trait. This trait will need to be imported to continue calling these."
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false }
+author = "jdisanti"
+
+[[smithy-rs]]
+message = "The `message()` and `code()` methods on errors have been moved into `ProvideErrorMetadata` trait. This trait will need to be imported to continue calling these."
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"}
+author = "jdisanti"
+
[[aws-sdk-rust]]
message = """
-Provide a way to retrieve fallback credentials if a call to `provide_credentials` is interrupted. An interrupt can occur when a timeout future is raced against a future for `provide_credentials`, and the former wins the race. A new method, `fallback_on_interrupt` on the `ProvideCredentials` trait, can be used in that case. The following code snippet from `LazyCredentialsCache::provide_cached_credentials` has been updated like so:
-Before:
+The `*Error` and `*ErrorKind` types have been combined to make error matching simpler.
+
+Example with S3
+**Before:**
```rust
-let timeout_future = self.sleeper.sleep(self.load_timeout);
-// --snip--
-let future = Timeout::new(provider.provide_credentials(), timeout_future);
-let result = cache
- .get_or_load(|| {
- async move {
- let credentials = future.await.map_err(|_err| {
- CredentialsError::provider_timed_out(load_timeout)
- })??;
- // --snip--
+let result = client
+ .get_object()
+ .bucket(BUCKET_NAME)
+ .key("some-key")
+ .send()
+ .await;
+match result {
+ Ok(_output) => { /* Do something with the output */ }
+ Err(err) => match err.into_service_error() {
+ GetObjectError { kind, .. } => match kind {
+ GetObjectErrorKind::InvalidObjectState(value) => println!("invalid object state: {:?}", value),
+ GetObjectErrorKind::NoSuchKey(_) => println!("object didn't exist"),
}
- }).await;
-// --snip--
+ err @ GetObjectError { .. } if err.code() == Some("SomeUnmodeledError") => {}
+ err @ _ => return Err(err.into()),
+ },
+}
```
-After:
+**After:**
```rust
-let timeout_future = self.sleeper.sleep(self.load_timeout);
-// --snip--
-let future = Timeout::new(provider.provide_credentials(), timeout_future);
-let result = cache
- .get_or_load(|| {
- async move {
- let credentials = match future.await {
- Ok(creds) => creds?,
- Err(_err) => match provider.fallback_on_interrupt() { // can provide fallback credentials
- Some(creds) => creds,
- None => return Err(CredentialsError::provider_timed_out(load_timeout)),
- }
- };
- // --snip--
+// Needed to access the `.code()` function on the error type:
+use aws_sdk_s3::types::ProvideErrorMetadata;
+let result = client
+ .get_object()
+ .bucket(BUCKET_NAME)
+ .key("some-key")
+ .send()
+ .await;
+match result {
+ Ok(_output) => { /* Do something with the output */ }
+ Err(err) => match err.into_service_error() {
+ GetObjectError::InvalidObjectState(value) => {
+ println!("invalid object state: {:?}", value);
+ }
+ GetObjectError::NoSuchKey(_) => {
+ println!("object didn't exist");
}
- }).await;
-// --snip--
+ err if err.code() == Some("SomeUnmodeledError") => {}
+ err @ _ => return Err(err.into()),
+ },
+}
```
+
"""
-references = ["smithy-rs#2246"]
-meta = { "breaking" = false, "tada" = false, "bug" = false }
-author = "ysaito1001"
+references = ["smithy-rs#76", "smithy-rs#2129", "smithy-rs#2075"]
+meta = { "breaking" = true, "tada" = false, "bug" = false }
+author = "jdisanti"
[[smithy-rs]]
-message = "The [`@uniqueItems`](https://smithy.io/2.0/spec/constraint-traits.html#uniqueitems-trait) trait on `list` shapes is now supported in server SDKs."
-references = ["smithy-rs#2232", "smithy-rs#1670"]
-meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "server"}
-author = "david-perez"
-
-[[aws-sdk-rust]]
message = """
-Add static stability support to IMDS credentials provider. It does not alter common use cases for the provider, but allows the provider to serve expired credentials in case IMDS is unreachable. This allows requests to be dispatched to a target service with expired credentials. This, in turn, allows the target service to make the ultimate decision as to whether requests sent are valid or not.
+The `*Error` and `*ErrorKind` types have been combined to make error matching simpler.
+
+Example with S3
+**Before:**
+```rust
+let result = client
+ .get_object()
+ .bucket(BUCKET_NAME)
+ .key("some-key")
+ .send()
+ .await;
+match result {
+ Ok(_output) => { /* Do something with the output */ }
+ Err(err) => match err.into_service_error() {
+ GetObjectError { kind, .. } => match kind {
+ GetObjectErrorKind::InvalidObjectState(value) => println!("invalid object state: {:?}", value),
+ GetObjectErrorKind::NoSuchKey(_) => println!("object didn't exist"),
+ }
+ err @ GetObjectError { .. } if err.code() == Some("SomeUnmodeledError") => {}
+ err @ _ => return Err(err.into()),
+ },
+}
+```
+**After:**
+```rust
+// Needed to access the `.code()` function on the error type:
+use aws_sdk_s3::types::ProvideErrorMetadata;
+let result = client
+ .get_object()
+ .bucket(BUCKET_NAME)
+ .key("some-key")
+ .send()
+ .await;
+match result {
+ Ok(_output) => { /* Do something with the output */ }
+ Err(err) => match err.into_service_error() {
+ GetObjectError::InvalidObjectState(value) => {
+ println!("invalid object state: {:?}", value);
+ }
+ GetObjectError::NoSuchKey(_) => {
+ println!("object didn't exist");
+ }
+ err if err.code() == Some("SomeUnmodeledError") => {}
+ err @ _ => return Err(err.into()),
+ },
+}
+```
+
"""
-references = ["smithy-rs#2258"]
-meta = { "breaking" = false, "tada" = true, "bug" = false }
-author = "ysaito1001"
+references = ["smithy-rs#76", "smithy-rs#2129", "smithy-rs#2075"]
+meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"}
+author = "jdisanti"
[[smithy-rs]]
-message = "Fix broken doc link for `tokio_stream::Stream` that is a re-export of `futures_core::Stream`."
-references = ["smithy-rs#2271"]
-meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client"}
-author = "ysaito1001"
+message = "`aws_smithy_types::Error` has been renamed to `aws_smithy_types::error::ErrorMetadata`."
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"}
+author = "jdisanti"
[[aws-sdk-rust]]
-message = "Fix broken doc link for `tokio_stream::Stream` that is a re-export of `futures_core::Stream`."
-references = ["smithy-rs#2271"]
-meta = { "breaking" = false, "tada" = false, "bug" = true }
-author = "ysaito1001"
+message = "`aws_smithy_types::Error` has been renamed to `aws_smithy_types::error::ErrorMetadata`."
+references = ["smithy-rs#76", "smithy-rs#2129"]
+meta = { "breaking" = true, "tada" = false, "bug" = false }
+author = "jdisanti"
+
+[[aws-sdk-rust]]
+message = "Fluent builder methods on the client are now marked as deprecated when the related operation is deprecated."
+references = ["aws-sdk-rust#740"]
+meta = { "breaking" = false, "tada" = true, "bug" = true }
+author = "Velfi"
+
+[[smithy-rs]]
+message = "Fluent builder methods on the client are now marked as deprecated when the related operation is deprecated."
+references = ["aws-sdk-rust#740"]
+meta = { "breaking" = false, "tada" = true, "bug" = true, "target" = "client"}
+author = "Velfi"
[[smithy-rs]]
message = """
-Fix `name` and `absolute` methods on `OperationExtension`.
-The older, [now removed](https://github.com/awslabs/smithy-rs/pull/2161), service builder would insert `OperationExtension` into the `http::Response` containing the [absolute shape ID](https://smithy.io/2.0/spec/model.html#grammar-token-smithy-AbsoluteRootShapeId) with the `#` symbol replaced with a `.`. When [reintroduced](https://github.com/awslabs/smithy-rs/pull/2157) into the new service builder machinery the behavior was changed - we now do _not_ perform the replace. This change fixes the documentation and `name`/`absolute` methods of the `OperationExtension` API to match this new behavior.
-In the old service builder, `OperationExtension` was initialized, by the framework, and then used as follows:
-```rust
-let ext = OperationExtension::new("com.amazonaws.CompleteSnapshot");
-// This is expected
-let name = ext.name(); // "CompleteSnapshot"
-let namespace = ext.namespace(); // = "com.amazonaws";
+Add support for the `awsQueryCompatible` trait. This allows services to continue supporting a custom error code (via the `awsQueryError` trait) when the services migrate their protocol from `awsQuery` to `awsJson1_0` annotated with `awsQueryCompatible`.
+
+Click to expand for more details...
+
+After the migration, services will include an additional header `x-amzn-query-error` in their responses whose value is in the form of `;`. An example response looks something like
```
-When reintroduced, `OperationExtension` was initialized by the `Plugin` and then used as follows:
-```rust
-let ext = OperationExtension::new("com.amazonaws#CompleteSnapshot");
-// This is the bug
-let name = ext.name(); // "amazonaws#CompleteSnapshot"
-let namespace = ext.namespace(); // = "com";
+HTTP/1.1 400
+x-amzn-query-error: AWS.SimpleQueueService.NonExistentQueue;Sender
+Date: Wed, 08 Sep 2021 23:46:52 GMT
+Content-Type: application/x-amz-json-1.0
+Content-Length: 163
+
+{
+ "__type": "com.amazonaws.sqs#QueueDoesNotExist",
+ "message": "some user-visible message"
+}
```
-The intended behavior is now restored:
+`` is `AWS.SimpleQueueService.NonExistentQueue` and `` is `Sender`.
+
+If an operation results in an error that causes a service to send back the response above, you can access `` and `` as follows:
```rust
-let ext = OperationExtension::new("com.amazonaws#CompleteSnapshot");
-// This is expected
-let name = ext.name(); // "CompleteSnapshot"
-let namespace = ext.namespace(); // = "com.amazonaws";
+match client.some_operation().send().await {
+ Ok(_) => { /* success */ }
+ Err(sdk_err) => {
+ let err = sdk_err.into_service_error();
+ assert_eq!(
+ error.meta().code(),
+ Some("AWS.SimpleQueueService.NonExistentQueue"),
+ );
+ assert_eq!(error.meta().extra("type"), Some("Sender"));
+ }
+}
+
```
-The rationale behind this change is that the previous design was tailored towards a specific internal use case and shouldn't be enforced on all customers.
"""
-references = ["smithy-rs#2276"]
-meta = { "breaking" = true, "tada" = false, "bug" = true, "target" = "server"}
-author = "hlbarber"
+references = ["smithy-rs#2398"]
+meta = { "breaking" = false, "tada" = true, "bug" = false }
+author = "ysaito1001"
[[aws-sdk-rust]]
-message = """
-Fix request canonicalization for HTTP requests with repeated headers (for example S3's `GetObjectAttributes`). Previously requests with repeated headers would fail with a 403 signature mismatch due to this bug.
-"""
-references = ["smithy-rs#2261", "aws-sdk-rust#720"]
-meta = { "breaking" = false, "tada" = false, "bug" = true }
-author = "nipunn1313"
+message = "`SdkError` variants can now be constructed for easier unit testing."
+references = ["smithy-rs#2428", "smithy-rs#2208"]
+meta = { "breaking" = false, "tada" = true, "bug" = false }
+author = "jdisanti"
[[smithy-rs]]
-message = """Add serde crate to `aws-smithy-types`.
-
-It's behind the feature gate `aws_sdk_unstable` which can only be enabled via a `--cfg` flag.
-"""
-references = ["smithy-rs#1944"]
-meta = { "breaking" = false, "tada" = false, "bug" = false }
-author = "thomas-k-cameron"
+message = "`SdkError` variants can now be constructed for easier unit testing."
+references = ["smithy-rs#2428", "smithy-rs#2208"]
+meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "client" }
+author = "jdisanti"
diff --git a/aws/SDK_CHANGELOG.next.json b/aws/SDK_CHANGELOG.next.json
index 5a4025621f7..5673dbc79f6 100644
--- a/aws/SDK_CHANGELOG.next.json
+++ b/aws/SDK_CHANGELOG.next.json
@@ -498,4 +498,4 @@
}
],
"aws-sdk-model": []
-}
\ No newline at end of file
+}
diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml
index 9a7dbc128e3..6935fe9bc0e 100644
--- a/aws/rust-runtime/aws-config/external-types.toml
+++ b/aws/rust-runtime/aws-config/external-types.toml
@@ -29,9 +29,6 @@ allowed_external_types = [
"http::uri::Uri",
"tower_service::Service",
- # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Decide if `InvalidUri` should be exposed
- "http::uri::InvalidUri",
-
# TODO(https://github.com/awslabs/smithy-rs/issues/1193): Decide if the following should be exposed
"hyper::client::connect::Connection",
"tokio::io::async_read::AsyncRead",
diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs
index ba5cea046cb..e791ec929a7 100644
--- a/aws/rust-runtime/aws-config/src/imds/client.rs
+++ b/aws/rust-runtime/aws-config/src/imds/client.rs
@@ -917,7 +917,7 @@ pub(crate) mod test {
imds_request("http://169.254.169.254/latest/metadata", TOKEN_A),
http::Response::builder()
.status(200)
- .body(SdkBody::from(vec![0xA0 as u8, 0xA1 as u8]))
+ .body(SdkBody::from(vec![0xA0, 0xA1]))
.unwrap(),
),
]);
diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs
index 8ca695a7700..fc82e6d1fe7 100644
--- a/aws/rust-runtime/aws-config/src/lib.rs
+++ b/aws/rust-runtime/aws-config/src/lib.rs
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_debug_implementations,
missing_docs,
diff --git a/aws/rust-runtime/aws-config/src/profile/credentials.rs b/aws/rust-runtime/aws-config/src/profile/credentials.rs
index ba7fb9241ed..9ce085503b4 100644
--- a/aws/rust-runtime/aws-config/src/profile/credentials.rs
+++ b/aws/rust-runtime/aws-config/src/profile/credentials.rs
@@ -61,9 +61,8 @@ impl ProvideCredentials for ProfileFileCredentialsProvider {
/// let provider = ProfileFileCredentialsProvider::builder().build();
/// ```
///
-/// _Note: Profile providers to not implement any caching. They will reload and reparse the profile
-/// from the file system when called. See [CredentialsCache](aws_credential_types::cache::CredentialsCache) for
-/// more information about caching._
+/// _Note: Profile providers, when called, will load and parse the profile from the file system
+/// only once. Parsed file contents will be cached indefinitely._
///
/// This provider supports several different credentials formats:
/// ### Credentials defined explicitly within the file
diff --git a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs
index 51449f22efa..f930b1a8d8f 100644
--- a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs
+++ b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs
@@ -3,14 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-use std::sync::Arc;
-
-use aws_sdk_sts::operation::AssumeRole;
-use aws_sdk_sts::{Config, Credentials};
-use aws_types::region::Region;
-
use super::repr::{self, BaseProvider};
-
use crate::credential_process::CredentialProcessProvider;
use crate::profile::credentials::ProfileFileError;
use crate::provider_config::ProviderConfig;
@@ -18,10 +11,13 @@ use crate::sso::{SsoConfig, SsoCredentialsProvider};
use crate::sts;
use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentialsProvider};
use aws_credential_types::provider::{self, error::CredentialsError, ProvideCredentials};
+use aws_sdk_sts::input::AssumeRoleInput;
use aws_sdk_sts::middleware::DefaultMiddleware;
+use aws_sdk_sts::{Config, Credentials};
use aws_smithy_client::erase::DynConnector;
-
+use aws_types::region::Region;
use std::fmt::Debug;
+use std::sync::Arc;
#[derive(Debug)]
pub(super) struct AssumeRoleProvider {
@@ -51,7 +47,7 @@ impl AssumeRoleProvider {
.as_ref()
.cloned()
.unwrap_or_else(|| sts::util::default_session_name("assume-role-from-profile"));
- let operation = AssumeRole::builder()
+ let operation = AssumeRoleInput::builder()
.role_arn(&self.role_arn)
.set_external_id(self.external_id.clone())
.role_session_name(session_name)
diff --git a/aws/rust-runtime/aws-config/src/sso.rs b/aws/rust-runtime/aws-config/src/sso.rs
index 0f58264645b..3881a217ab9 100644
--- a/aws/rust-runtime/aws-config/src/sso.rs
+++ b/aws/rust-runtime/aws-config/src/sso.rs
@@ -211,7 +211,7 @@ async fn load_sso_credentials(
let config = aws_sdk_sso::Config::builder()
.region(sso_config.region.clone())
.build();
- let operation = aws_sdk_sso::operation::GetRoleCredentials::builder()
+ let operation = aws_sdk_sso::input::GetRoleCredentialsInput::builder()
.role_name(&sso_config.role_name)
.access_token(&*token.access_token)
.account_id(&sso_config.account_id)
diff --git a/aws/rust-runtime/aws-config/src/sts/assume_role.rs b/aws/rust-runtime/aws-config/src/sts/assume_role.rs
index 422b6441515..ad24b11bb56 100644
--- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs
+++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs
@@ -5,19 +5,18 @@
//! Assume credentials for a role through the AWS Security Token Service (STS).
+use crate::provider_config::ProviderConfig;
use aws_credential_types::cache::CredentialsCache;
use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
-use aws_sdk_sts::error::AssumeRoleErrorKind;
+use aws_sdk_sts::error::AssumeRoleError;
+use aws_sdk_sts::input::AssumeRoleInput;
use aws_sdk_sts::middleware::DefaultMiddleware;
use aws_sdk_sts::model::PolicyDescriptorType;
-use aws_sdk_sts::operation::AssumeRole;
use aws_smithy_client::erase::DynConnector;
use aws_smithy_http::result::SdkError;
+use aws_smithy_types::error::display::DisplayErrorContext;
use aws_types::region::Region;
use std::time::Duration;
-
-use crate::provider_config::ProviderConfig;
-use aws_smithy_types::error::display::DisplayErrorContext;
use tracing::Instrument;
/// Credentials provider that uses credentials provided by another provider to assume a role
@@ -225,7 +224,7 @@ impl AssumeRoleProviderBuilder {
.session_name
.unwrap_or_else(|| super::util::default_session_name("assume-role-provider"));
- let operation = AssumeRole::builder()
+ let operation = AssumeRoleInput::builder()
.set_role_arn(Some(self.role_arn))
.set_external_id(self.external_id)
.set_role_session_name(Some(session_name))
@@ -266,9 +265,9 @@ impl Inner {
}
Err(SdkError::ServiceError(ref context))
if matches!(
- context.err().kind,
- AssumeRoleErrorKind::RegionDisabledException(_)
- | AssumeRoleErrorKind::MalformedPolicyDocumentException(_)
+ context.err(),
+ AssumeRoleError::RegionDisabledException(_)
+ | AssumeRoleError::MalformedPolicyDocumentException(_)
) =>
{
Err(CredentialsError::invalid_configuration(
diff --git a/aws/rust-runtime/aws-config/src/web_identity_token.rs b/aws/rust-runtime/aws-config/src/web_identity_token.rs
index e3c45c7bb8a..82c184897fb 100644
--- a/aws/rust-runtime/aws-config/src/web_identity_token.rs
+++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs
@@ -236,7 +236,7 @@ async fn load_credentials(
.region(region.clone())
.build();
- let operation = aws_sdk_sts::operation::AssumeRoleWithWebIdentity::builder()
+ let operation = aws_sdk_sts::input::AssumeRoleWithWebIdentityInput::builder()
.role_arn(role_arn)
.role_session_name(session_name)
.web_identity_token(token)
diff --git a/aws/rust-runtime/aws-credential-types/Cargo.toml b/aws/rust-runtime/aws-credential-types/Cargo.toml
index bf7e3325681..dd241e764ec 100644
--- a/aws/rust-runtime/aws-credential-types/Cargo.toml
+++ b/aws/rust-runtime/aws-credential-types/Cargo.toml
@@ -14,6 +14,7 @@ test-util = []
[dependencies]
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" }
aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" }
+fastrand = "1.4.0"
tokio = { version = "1.8.4", features = ["sync"] }
tracing = "0.1"
zeroize = "1"
diff --git a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs
index 3a2459c5974..1081b8f3364 100644
--- a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs
+++ b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs
@@ -20,6 +20,7 @@ use crate::time_source::TimeSource;
const DEFAULT_LOAD_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_CREDENTIAL_EXPIRATION: Duration = Duration::from_secs(15 * 60);
const DEFAULT_BUFFER_TIME: Duration = Duration::from_secs(10);
+const DEFAULT_BUFFER_TIME_JITTER_FRACTION: fn() -> f64 = fastrand::f64;
#[derive(Debug)]
pub(crate) struct LazyCredentialsCache {
@@ -28,6 +29,8 @@ pub(crate) struct LazyCredentialsCache {
cache: ExpiringCache,
provider: SharedCredentialsProvider,
load_timeout: Duration,
+ buffer_time: Duration,
+ buffer_time_jitter_fraction: fn() -> f64,
default_credential_expiration: Duration,
}
@@ -37,8 +40,9 @@ impl LazyCredentialsCache {
sleeper: Arc,
provider: SharedCredentialsProvider,
load_timeout: Duration,
- default_credential_expiration: Duration,
buffer_time: Duration,
+ buffer_time_jitter_fraction: fn() -> f64,
+ default_credential_expiration: Duration,
) -> Self {
Self {
time,
@@ -46,6 +50,8 @@ impl LazyCredentialsCache {
cache: ExpiringCache::new(buffer_time),
provider,
load_timeout,
+ buffer_time,
+ buffer_time_jitter_fraction,
default_credential_expiration,
}
}
@@ -95,17 +101,28 @@ impl ProvideCachedCredentials for LazyCredentialsCache {
let expiry = credentials
.expiry()
.unwrap_or(now + default_credential_expiration);
- Ok((credentials, expiry))
+
+ let jitter = self
+ .buffer_time
+ .mul_f64((self.buffer_time_jitter_fraction)());
+
+ // Logging for cache miss should be emitted here as opposed to after the call to
+ // `cache.get_or_load` above. In the case of multiple threads concurrently executing
+ // `cache.get_or_load`, logging inside `cache.get_or_load` ensures that it is emitted
+ // only once for the first thread that succeeds in populating a cache value.
+ info!(
+ "credentials cache miss occurred; added new AWS credentials (took {:?})",
+ start_time.elapsed()
+ );
+
+ Ok((credentials, expiry + jitter))
}
// Only instrument the the actual load future so that no span
// is opened if the cache decides not to execute it.
.instrument(span)
})
.await;
- info!(
- "credentials cache miss occurred; retrieved new AWS credentials (took {:?})",
- start_time.elapsed()
- );
+ debug!("loaded credentials");
result
}
})
@@ -125,8 +142,8 @@ mod builder {
use super::TimeSource;
use super::{
- LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_CREDENTIAL_EXPIRATION,
- DEFAULT_LOAD_TIMEOUT,
+ LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_BUFFER_TIME_JITTER_FRACTION,
+ DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_LOAD_TIMEOUT,
};
/// Builder for constructing a `LazyCredentialsCache`.
@@ -147,6 +164,7 @@ mod builder {
time_source: Option,
load_timeout: Option,
buffer_time: Option,
+ buffer_time_jitter_fraction: Option f64>,
default_credential_expiration: Option,
}
@@ -228,6 +246,38 @@ mod builder {
self
}
+ /// A random percentage by which buffer time is jittered for randomization.
+ ///
+ /// For example, if credentials are expiring in 15 minutes, the buffer time is 10 seconds,
+ /// and buffer time jitter fraction is 0.2, then buffer time is adjusted to 8 seconds.
+ /// Therefore, any requests made after 14 minutes and 52 seconds will load new credentials.
+ ///
+ /// Defaults to a randomly generated value between 0.0 and 1.0. This setter is for testing only.
+ #[cfg(feature = "test-util")]
+ pub fn buffer_time_jitter_fraction(
+ mut self,
+ buffer_time_jitter_fraction: fn() -> f64,
+ ) -> Self {
+ self.set_buffer_time_jitter_fraction(Some(buffer_time_jitter_fraction));
+ self
+ }
+
+ /// A random percentage by which buffer time is jittered for randomization.
+ ///
+ /// For example, if credentials are expiring in 15 minutes, the buffer time is 10 seconds,
+ /// and buffer time jitter fraction is 0.2, then buffer time is adjusted to 8 seconds.
+ /// Therefore, any requests made after 14 minutes and 52 seconds will load new credentials.
+ ///
+ /// Defaults to a randomly generated value between 0.0 and 1.0. This setter is for testing only.
+ #[cfg(feature = "test-util")]
+ pub fn set_buffer_time_jitter_fraction(
+ &mut self,
+ buffer_time_jitter_fraction: Option f64>,
+ ) -> &mut Self {
+ self.buffer_time_jitter_fraction = buffer_time_jitter_fraction;
+ self
+ }
+
/// Default expiration time to set on credentials if they don't have an expiration time.
///
/// This is only used if the given [`ProvideCredentials`](crate::provider::ProvideCredentials) returns
@@ -283,8 +333,10 @@ mod builder {
}),
provider,
self.load_timeout.unwrap_or(DEFAULT_LOAD_TIMEOUT),
- default_credential_expiration,
self.buffer_time.unwrap_or(DEFAULT_BUFFER_TIME),
+ self.buffer_time_jitter_fraction
+ .unwrap_or(DEFAULT_BUFFER_TIME_JITTER_FRACTION),
+ default_credential_expiration,
)
}
}
@@ -310,8 +362,11 @@ mod tests {
DEFAULT_LOAD_TIMEOUT,
};
+ const BUFFER_TIME_NO_JITTER: fn() -> f64 = || 0_f64;
+
fn test_provider(
time: TimeSource,
+ buffer_time_jitter_fraction: fn() -> f64,
load_list: Vec,
) -> LazyCredentialsCache {
let load_list = Arc::new(Mutex::new(load_list));
@@ -327,8 +382,9 @@ mod tests {
}
})),
DEFAULT_LOAD_TIMEOUT,
- DEFAULT_CREDENTIAL_EXPIRATION,
DEFAULT_BUFFER_TIME,
+ buffer_time_jitter_fraction,
+ DEFAULT_CREDENTIAL_EXPIRATION,
)
}
@@ -361,8 +417,9 @@ mod tests {
Arc::new(TokioSleep::new()),
provider,
DEFAULT_LOAD_TIMEOUT,
- DEFAULT_CREDENTIAL_EXPIRATION,
DEFAULT_BUFFER_TIME,
+ BUFFER_TIME_NO_JITTER,
+ DEFAULT_CREDENTIAL_EXPIRATION,
);
assert_eq!(
epoch_secs(1000),
@@ -381,6 +438,7 @@ mod tests {
let mut time = TestingTimeSource::new(epoch_secs(100));
let credentials_cache = test_provider(
TimeSource::testing(&time),
+ BUFFER_TIME_NO_JITTER,
vec![
Ok(credentials(1000)),
Ok(credentials(2000)),
@@ -404,6 +462,7 @@ mod tests {
let mut time = TestingTimeSource::new(epoch_secs(100));
let credentials_cache = test_provider(
TimeSource::testing(&time),
+ BUFFER_TIME_NO_JITTER,
vec![
Ok(credentials(1000)),
Err(CredentialsError::not_loaded("failed")),
@@ -430,6 +489,7 @@ mod tests {
let time = TestingTimeSource::new(epoch_secs(0));
let credentials_cache = Arc::new(test_provider(
TimeSource::testing(&time),
+ BUFFER_TIME_NO_JITTER,
vec![
Ok(credentials(500)),
Ok(credentials(1500)),
@@ -480,8 +540,9 @@ mod tests {
Ok(credentials(1000))
})),
Duration::from_millis(5),
- DEFAULT_CREDENTIAL_EXPIRATION,
DEFAULT_BUFFER_TIME,
+ BUFFER_TIME_NO_JITTER,
+ DEFAULT_CREDENTIAL_EXPIRATION,
);
assert!(matches!(
@@ -489,4 +550,30 @@ mod tests {
Err(CredentialsError::ProviderTimedOut { .. })
));
}
+
+ #[tokio::test]
+ async fn buffer_time_jitter() {
+ let mut time = TestingTimeSource::new(epoch_secs(100));
+ let buffer_time_jitter_fraction = || 0.5_f64;
+ let credentials_cache = test_provider(
+ TimeSource::testing(&time),
+ buffer_time_jitter_fraction,
+ vec![Ok(credentials(1000)), Ok(credentials(2000))],
+ );
+
+ expect_creds(1000, &credentials_cache).await;
+ let buffer_time_with_jitter =
+ (DEFAULT_BUFFER_TIME.as_secs_f64() * buffer_time_jitter_fraction()) as u64;
+ assert_eq!(buffer_time_with_jitter, 5);
+ // Advance time to the point where the first credentials are about to expire (but haven't).
+ let almost_expired_secs = 1000 - buffer_time_with_jitter - 1;
+ time.set_time(epoch_secs(almost_expired_secs));
+ // We should still use the first credentials.
+ expect_creds(1000, &credentials_cache).await;
+ // Now let the first credentials expire.
+ let expired_secs = almost_expired_secs + 1;
+ time.set_time(epoch_secs(expired_secs));
+ // Now that the first credentials have been expired, the second credentials will be retrieved.
+ expect_creds(2000, &credentials_cache).await;
+ }
}
diff --git a/aws/rust-runtime/aws-credential-types/src/lib.rs b/aws/rust-runtime/aws-credential-types/src/lib.rs
index 1f790c3fca5..b2f8330b589 100644
--- a/aws/rust-runtime/aws-credential-types/src/lib.rs
+++ b/aws/rust-runtime/aws-credential-types/src/lib.rs
@@ -8,6 +8,7 @@
//! * An opaque struct representing credentials
//! * Concrete implementations of credentials caching
+#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_debug_implementations,
missing_docs,
diff --git a/aws/rust-runtime/aws-endpoint/src/lib.rs b/aws/rust-runtime/aws-endpoint/src/lib.rs
index c8951431845..deb4b6b670b 100644
--- a/aws/rust-runtime/aws-endpoint/src/lib.rs
+++ b/aws/rust-runtime/aws-endpoint/src/lib.rs
@@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+#![allow(clippy::derive_partial_eq_without_eq)]
+
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
@@ -270,7 +272,7 @@ mod test {
let mut req = operation::Request::new(req);
{
let mut props = req.properties_mut();
- props.insert(region.clone());
+ props.insert(region);
props.insert(SigningService::from_static("qldb"));
props.insert(endpoint);
};
diff --git a/aws/rust-runtime/aws-http/src/auth.rs b/aws/rust-runtime/aws-http/src/auth.rs
index c91b4c5bb51..98e0e219bb0 100644
--- a/aws/rust-runtime/aws-http/src/auth.rs
+++ b/aws/rust-runtime/aws-http/src/auth.rs
@@ -188,10 +188,7 @@ mod tests {
.create_cache(SharedCredentialsProvider::new(provide_credentials_fn(
|| async { Ok(Credentials::for_tests()) },
)));
- set_credentials_cache(
- &mut req.properties_mut(),
- SharedCredentialsCache::from(credentials_cache),
- );
+ set_credentials_cache(&mut req.properties_mut(), credentials_cache);
let req = CredentialsStage::new()
.apply(req)
.await
diff --git a/aws/rust-runtime/aws-http/src/lib.rs b/aws/rust-runtime/aws-http/src/lib.rs
index b000c3d6aea..d5307bcba3d 100644
--- a/aws/rust-runtime/aws-http/src/lib.rs
+++ b/aws/rust-runtime/aws-http/src/lib.rs
@@ -3,8 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
-//! Provides user agent and credentials middleware for the AWS SDK.
+//! AWS-specific middleware implementations and HTTP-related features.
+#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
@@ -27,3 +28,6 @@ pub mod user_agent;
/// AWS-specific content-encoding tools
pub mod content_encoding;
+
+/// AWS-specific request ID support
+pub mod request_id;
diff --git a/aws/rust-runtime/aws-http/src/request_id.rs b/aws/rust-runtime/aws-http/src/request_id.rs
new file mode 100644
index 00000000000..c3f19272280
--- /dev/null
+++ b/aws/rust-runtime/aws-http/src/request_id.rs
@@ -0,0 +1,182 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+use aws_smithy_http::http::HttpHeaders;
+use aws_smithy_http::operation;
+use aws_smithy_http::result::SdkError;
+use aws_smithy_types::error::metadata::{
+ Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata,
+};
+use aws_smithy_types::error::Unhandled;
+use http::{HeaderMap, HeaderValue};
+
+/// Constant for the [`ErrorMetadata`] extra field that contains the request ID
+const AWS_REQUEST_ID: &str = "aws_request_id";
+
+/// Implementers add a function to return an AWS request ID
+pub trait RequestId {
+ /// Returns the request ID, or `None` if the service could not be reached.
+ fn request_id(&self) -> Option<&str>;
+}
+
+impl RequestId for SdkError
+where
+ R: HttpHeaders,
+{
+ fn request_id(&self) -> Option<&str> {
+ match self {
+ Self::ResponseError(err) => extract_request_id(err.raw().http_headers()),
+ Self::ServiceError(err) => extract_request_id(err.raw().http_headers()),
+ _ => None,
+ }
+ }
+}
+
+impl RequestId for ErrorMetadata {
+ fn request_id(&self) -> Option<&str> {
+ self.extra(AWS_REQUEST_ID)
+ }
+}
+
+impl RequestId for Unhandled {
+ fn request_id(&self) -> Option<&str> {
+ self.meta().request_id()
+ }
+}
+
+impl RequestId for operation::Response {
+ fn request_id(&self) -> Option<&str> {
+ extract_request_id(self.http().headers())
+ }
+}
+
+impl RequestId for http::Response {
+ fn request_id(&self) -> Option<&str> {
+ extract_request_id(self.headers())
+ }
+}
+
+impl RequestId for Result
+where
+ O: RequestId,
+ E: RequestId,
+{
+ fn request_id(&self) -> Option<&str> {
+ match self {
+ Ok(ok) => ok.request_id(),
+ Err(err) => err.request_id(),
+ }
+ }
+}
+
+/// Applies a request ID to a generic error builder
+#[doc(hidden)]
+pub fn apply_request_id(
+ builder: ErrorMetadataBuilder,
+ headers: &HeaderMap,
+) -> ErrorMetadataBuilder {
+ if let Some(request_id) = extract_request_id(headers) {
+ builder.custom(AWS_REQUEST_ID, request_id)
+ } else {
+ builder
+ }
+}
+
+/// Extracts a request ID from HTTP response headers
+fn extract_request_id(headers: &HeaderMap) -> Option<&str> {
+ headers
+ .get("x-amzn-requestid")
+ .or_else(|| headers.get("x-amz-request-id"))
+ .and_then(|value| value.to_str().ok())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aws_smithy_http::body::SdkBody;
+ use http::Response;
+
+ #[test]
+ fn test_request_id_sdk_error() {
+ let without_request_id =
+ || operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap());
+ let with_request_id = || {
+ operation::Response::new(
+ Response::builder()
+ .header(
+ "x-amzn-requestid",
+ HeaderValue::from_static("some-request-id"),
+ )
+ .body(SdkBody::empty())
+ .unwrap(),
+ )
+ };
+ assert_eq!(
+ None,
+ SdkError::<(), _>::response_error("test", without_request_id()).request_id()
+ );
+ assert_eq!(
+ Some("some-request-id"),
+ SdkError::<(), _>::response_error("test", with_request_id()).request_id()
+ );
+ assert_eq!(
+ None,
+ SdkError::service_error((), without_request_id()).request_id()
+ );
+ assert_eq!(
+ Some("some-request-id"),
+ SdkError::service_error((), with_request_id()).request_id()
+ );
+ }
+
+ #[test]
+ fn test_extract_request_id() {
+ let mut headers = HeaderMap::new();
+ assert_eq!(None, extract_request_id(&headers));
+
+ headers.append(
+ "x-amzn-requestid",
+ HeaderValue::from_static("some-request-id"),
+ );
+ assert_eq!(Some("some-request-id"), extract_request_id(&headers));
+
+ headers.append(
+ "x-amz-request-id",
+ HeaderValue::from_static("other-request-id"),
+ );
+ assert_eq!(Some("some-request-id"), extract_request_id(&headers));
+
+ headers.remove("x-amzn-requestid");
+ assert_eq!(Some("other-request-id"), extract_request_id(&headers));
+ }
+
+ #[test]
+ fn test_apply_request_id() {
+ let mut headers = HeaderMap::new();
+ assert_eq!(
+ ErrorMetadata::builder().build(),
+ apply_request_id(ErrorMetadata::builder(), &headers).build(),
+ );
+
+ headers.append(
+ "x-amzn-requestid",
+ HeaderValue::from_static("some-request-id"),
+ );
+ assert_eq!(
+ ErrorMetadata::builder()
+ .custom(AWS_REQUEST_ID, "some-request-id")
+ .build(),
+ apply_request_id(ErrorMetadata::builder(), &headers).build(),
+ );
+ }
+
+ #[test]
+ fn test_error_metadata_request_id_impl() {
+ let err = ErrorMetadata::builder()
+ .custom(AWS_REQUEST_ID, "some-request-id")
+ .build();
+ assert_eq!(Some("some-request-id"), err.request_id());
+ }
+}
diff --git a/aws/rust-runtime/aws-inlineable/src/http_body_checksum.rs b/aws/rust-runtime/aws-inlineable/src/http_body_checksum.rs
index 59dea2ca41d..d99e1b11231 100644
--- a/aws/rust-runtime/aws-inlineable/src/http_body_checksum.rs
+++ b/aws/rust-runtime/aws-inlineable/src/http_body_checksum.rs
@@ -269,7 +269,7 @@ mod tests {
for i in 0..10000 {
let line = format!("This is a large file created for testing purposes {}", i);
- file.as_file_mut().write(line.as_bytes()).unwrap();
+ file.as_file_mut().write_all(line.as_bytes()).unwrap();
crc32c_checksum.update(line.as_bytes());
}
diff --git a/aws/rust-runtime/aws-inlineable/src/lib.rs b/aws/rust-runtime/aws-inlineable/src/lib.rs
index b4e00994d41..d5ed3b8be39 100644
--- a/aws/rust-runtime/aws-inlineable/src/lib.rs
+++ b/aws/rust-runtime/aws-inlineable/src/lib.rs
@@ -10,6 +10,7 @@
//! This is _NOT_ intended to be an actual crate. It is a cargo project to solely to aid
//! with local development of the SDK.
+#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
@@ -23,9 +24,11 @@ pub mod no_credentials;
/// Support types required for adding presigning to an operation in a generated service.
pub mod presigning;
+// TODO(CrateReorganization): Delete the `old_presigning` module
+pub mod old_presigning;
-/// Special logic for handling S3's error responses.
-pub mod s3_errors;
+/// Special logic for extracting request IDs from S3's responses.
+pub mod s3_request_id;
/// Glacier-specific checksumming behavior
pub mod glacier_checksums;
diff --git a/aws/rust-runtime/aws-inlineable/src/old_presigning.rs b/aws/rust-runtime/aws-inlineable/src/old_presigning.rs
new file mode 100644
index 00000000000..cf95c3901d1
--- /dev/null
+++ b/aws/rust-runtime/aws-inlineable/src/old_presigning.rs
@@ -0,0 +1,282 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//! Presigned request types and configuration.
+
+/// Presigning config and builder
+pub mod config {
+ use std::fmt;
+ use std::time::{Duration, SystemTime};
+
+ const ONE_WEEK: Duration = Duration::from_secs(604800);
+
+ /// Presigning config values required for creating a presigned request.
+ #[non_exhaustive]
+ #[derive(Debug, Clone)]
+ pub struct PresigningConfig {
+ start_time: SystemTime,
+ expires_in: Duration,
+ }
+
+ impl PresigningConfig {
+ /// Creates a `PresigningConfig` with the given `expires_in` duration.
+ ///
+ /// The `expires_in` duration is the total amount of time the presigned request should
+ /// be valid for. Other config values are defaulted.
+ ///
+ /// Credential expiration time takes priority over the `expires_in` value.
+ /// If the credentials used to sign the request expire before the presigned request is
+ /// set to expire, then the presigned request will become invalid.
+ pub fn expires_in(expires_in: Duration) -> Result {
+ Self::builder().expires_in(expires_in).build()
+ }
+
+ /// Creates a new builder for creating a `PresigningConfig`.
+ pub fn builder() -> Builder {
+ Builder::default()
+ }
+
+ /// Returns the amount of time the presigned request should be valid for.
+ pub fn expires(&self) -> Duration {
+ self.expires_in
+ }
+
+ /// Returns the start time. The presigned request will be valid between this and the end
+ /// time produced by adding the `expires()` value to it.
+ pub fn start_time(&self) -> SystemTime {
+ self.start_time
+ }
+ }
+
+ #[derive(Debug)]
+ enum ErrorKind {
+ /// Presigned requests cannot be valid for longer than one week.
+ ExpiresInDurationTooLong,
+
+ /// The `PresigningConfig` builder requires a value for `expires_in`.
+ ExpiresInRequired,
+ }
+
+ /// `PresigningConfig` build errors.
+ #[derive(Debug)]
+ pub struct Error {
+ kind: ErrorKind,
+ }
+
+ impl std::error::Error for Error {}
+
+ impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.kind {
+ ErrorKind::ExpiresInDurationTooLong => {
+ write!(f, "`expires_in` must be no longer than one week")
+ }
+ ErrorKind::ExpiresInRequired => write!(f, "`expires_in` is required"),
+ }
+ }
+ }
+
+ impl From for Error {
+ fn from(kind: ErrorKind) -> Self {
+ Self { kind }
+ }
+ }
+
+ /// Builder used to create `PresigningConfig`.
+ #[non_exhaustive]
+ #[derive(Default, Debug)]
+ pub struct Builder {
+ start_time: Option,
+ expires_in: Option,
+ }
+
+ impl Builder {
+ /// Sets the start time for the presigned request.
+ ///
+ /// The request will start to be valid at this time, and will cease to be valid after
+ /// the end time, which can be determined by adding the `expires_in` duration to this
+ /// start time. If not specified, this will default to the current time.
+ ///
+ /// Optional.
+ pub fn start_time(mut self, start_time: SystemTime) -> Self {
+ self.set_start_time(Some(start_time));
+ self
+ }
+
+ /// Sets the start time for the presigned request.
+ ///
+ /// The request will start to be valid at this time, and will cease to be valid after
+ /// the end time, which can be determined by adding the `expires_in` duration to this
+ /// start time. If not specified, this will default to the current time.
+ ///
+ /// Optional.
+ pub fn set_start_time(&mut self, start_time: Option) {
+ self.start_time = start_time;
+ }
+
+ /// Sets how long the request should be valid after the `start_time` (which defaults
+ /// to the current time).
+ ///
+ /// Credential expiration time takes priority over the `expires_in` value.
+ /// If the credentials used to sign the request expire before the presigned request is
+ /// set to expire, then the presigned request will become invalid.
+ ///
+ /// Required.
+ pub fn expires_in(mut self, expires_in: Duration) -> Self {
+ self.set_expires_in(Some(expires_in));
+ self
+ }
+
+ /// Sets how long the request should be valid after the `start_time` (which defaults
+ /// to the current time).
+ ///
+ /// Credential expiration time takes priority over the `expires_in` value.
+ /// If the credentials used to sign the request expire before the presigned request is
+ /// set to expire, then the presigned request will become invalid.
+ ///
+ /// Required.
+ pub fn set_expires_in(&mut self, expires_in: Option) {
+ self.expires_in = expires_in;
+ }
+
+ /// Builds the `PresigningConfig`. This will error if `expires_in` is not
+ /// given, or if it's longer than one week.
+ pub fn build(self) -> Result {
+ let expires_in = self.expires_in.ok_or(ErrorKind::ExpiresInRequired)?;
+ if expires_in > ONE_WEEK {
+ return Err(ErrorKind::ExpiresInDurationTooLong.into());
+ }
+ Ok(PresigningConfig {
+ start_time: self.start_time.unwrap_or_else(SystemTime::now),
+ expires_in,
+ })
+ }
+ }
+}
+
+/// Presigned request
+pub mod request {
+ use std::fmt::{Debug, Formatter};
+
+ /// Represents a presigned request. This only includes the HTTP request method, URI, and headers.
+ ///
+ /// **This struct has conversion convenience functions:**
+ ///
+ /// - [`PresignedRequest::to_http_request`][Self::to_http_request] returns an [`http::Request`](https://docs.rs/http/0.2.6/http/request/struct.Request.html)
+ /// - [`PresignedRequest::into`](#impl-From) returns an [`http::request::Builder`](https://docs.rs/http/0.2.6/http/request/struct.Builder.html)
+ #[non_exhaustive]
+ pub struct PresignedRequest(http::Request<()>);
+
+ impl PresignedRequest {
+ pub(crate) fn new(inner: http::Request<()>) -> Self {
+ Self(inner)
+ }
+
+ /// Returns the HTTP request method.
+ pub fn method(&self) -> &http::Method {
+ self.0.method()
+ }
+
+ /// Returns the HTTP request URI.
+ pub fn uri(&self) -> &http::Uri {
+ self.0.uri()
+ }
+
+ /// Returns any HTTP headers that need to go along with the request, except for `Host`,
+ /// which should be sent based on the endpoint in the URI by the HTTP client rather than
+ /// added directly.
+ pub fn headers(&self) -> &http::HeaderMap {
+ self.0.headers()
+ }
+
+ /// Given a body, convert this `PresignedRequest` into an `http::Request`
+ pub fn to_http_request(self, body: B) -> Result, http::Error> {
+ let builder: http::request::Builder = self.into();
+
+ builder.body(body)
+ }
+ }
+
+ impl Debug for PresignedRequest {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("PresignedRequest")
+ .field("method", self.method())
+ .field("uri", self.uri())
+ .field("headers", self.headers())
+ .finish()
+ }
+ }
+
+ impl From for http::request::Builder {
+ fn from(req: PresignedRequest) -> Self {
+ let mut builder = http::request::Builder::new()
+ .uri(req.uri())
+ .method(req.method());
+
+ if let Some(headers) = builder.headers_mut() {
+ *headers = req.headers().clone();
+ }
+
+ builder
+ }
+ }
+}
+
+/// Tower middleware service for creating presigned requests
+#[allow(dead_code)]
+pub(crate) mod service {
+ use super::request::PresignedRequest;
+ use aws_smithy_http::operation;
+ use http::header::USER_AGENT;
+ use std::future::{ready, Ready};
+ use std::marker::PhantomData;
+ use std::task::{Context, Poll};
+
+ /// Tower [`Service`](tower::Service) for generated a [`PresignedRequest`] from the AWS middleware.
+ #[derive(Default, Debug)]
+ #[non_exhaustive]
+ pub(crate) struct PresignedRequestService {
+ _phantom: PhantomData,
+ }
+
+ // Required because of the derive Clone on MapRequestService.
+ // Manually implemented to avoid requiring errors to implement Clone.
+ impl Clone for PresignedRequestService {
+ fn clone(&self) -> Self {
+ Self {
+ _phantom: Default::default(),
+ }
+ }
+ }
+
+ impl PresignedRequestService {
+ /// Creates a new `PresignedRequestService`
+ pub(crate) fn new() -> Self {
+ Self {
+ _phantom: Default::default(),
+ }
+ }
+ }
+
+ impl tower::Service for PresignedRequestService {
+ type Response = PresignedRequest;
+ type Error = E;
+ type Future = Ready>;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, req: operation::Request) -> Self::Future {
+ let (mut req, _) = req.into_parts();
+
+ // Remove user agent headers since the request will not be executed by the AWS Rust SDK.
+ req.headers_mut().remove(USER_AGENT);
+ req.headers_mut().remove("X-Amz-User-Agent");
+
+ ready(Ok(PresignedRequest::new(req.map(|_| ()))))
+ }
+ }
+}
diff --git a/aws/rust-runtime/aws-inlineable/src/presigning.rs b/aws/rust-runtime/aws-inlineable/src/presigning.rs
index 5a97a19902e..da0997d5918 100644
--- a/aws/rust-runtime/aws-inlineable/src/presigning.rs
+++ b/aws/rust-runtime/aws-inlineable/src/presigning.rs
@@ -5,229 +5,221 @@
//! Presigned request types and configuration.
-/// Presigning config and builder
-pub mod config {
- use std::fmt;
- use std::time::{Duration, SystemTime};
+use std::fmt;
+use std::time::{Duration, SystemTime};
- const ONE_WEEK: Duration = Duration::from_secs(604800);
+const ONE_WEEK: Duration = Duration::from_secs(604800);
- /// Presigning config values required for creating a presigned request.
- #[non_exhaustive]
- #[derive(Debug, Clone)]
- pub struct PresigningConfig {
- start_time: SystemTime,
- expires_in: Duration,
- }
+/// Presigning config values required for creating a presigned request.
+#[non_exhaustive]
+#[derive(Debug, Clone)]
+pub struct PresigningConfig {
+ start_time: SystemTime,
+ expires_in: Duration,
+}
- impl PresigningConfig {
- /// Creates a `PresigningConfig` with the given `expires_in` duration.
- ///
- /// The `expires_in` duration is the total amount of time the presigned request should
- /// be valid for. Other config values are defaulted.
- ///
- /// Credential expiration time takes priority over the `expires_in` value.
- /// If the credentials used to sign the request expire before the presigned request is
- /// set to expire, then the presigned request will become invalid.
- pub fn expires_in(expires_in: Duration) -> Result {
- Self::builder().expires_in(expires_in).build()
- }
+impl PresigningConfig {
+ /// Creates a `PresigningConfig` with the given `expires_in` duration.
+ ///
+ /// The `expires_in` duration is the total amount of time the presigned request should
+ /// be valid for. Other config values are defaulted.
+ ///
+ /// Credential expiration time takes priority over the `expires_in` value.
+ /// If the credentials used to sign the request expire before the presigned request is
+ /// set to expire, then the presigned request will become invalid.
+ pub fn expires_in(expires_in: Duration) -> Result {
+ Self::builder().expires_in(expires_in).build()
+ }
- /// Creates a new builder for creating a `PresigningConfig`.
- pub fn builder() -> Builder {
- Builder::default()
- }
+ /// Creates a new builder for creating a `PresigningConfig`.
+ pub fn builder() -> PresigningConfigBuilder {
+ PresigningConfigBuilder::default()
+ }
- /// Returns the amount of time the presigned request should be valid for.
- pub fn expires(&self) -> Duration {
- self.expires_in
- }
+ /// Returns the amount of time the presigned request should be valid for.
+ pub fn expires(&self) -> Duration {
+ self.expires_in
+ }
- /// Returns the start time. The presigned request will be valid between this and the end
- /// time produced by adding the `expires()` value to it.
- pub fn start_time(&self) -> SystemTime {
- self.start_time
- }
+ /// Returns the start time. The presigned request will be valid between this and the end
+ /// time produced by adding the `expires()` value to it.
+ pub fn start_time(&self) -> SystemTime {
+ self.start_time
}
+}
- #[derive(Debug)]
- enum ErrorKind {
- /// Presigned requests cannot be valid for longer than one week.
- ExpiresInDurationTooLong,
+#[derive(Debug)]
+enum ErrorKind {
+ /// Presigned requests cannot be valid for longer than one week.
+ ExpiresInDurationTooLong,
- /// The `PresigningConfig` builder requires a value for `expires_in`.
- ExpiresInRequired,
- }
+ /// The `PresigningConfig` builder requires a value for `expires_in`.
+ ExpiresInRequired,
+}
- /// `PresigningConfig` build errors.
- #[derive(Debug)]
- pub struct Error {
- kind: ErrorKind,
- }
+/// `PresigningConfig` build errors.
+#[derive(Debug)]
+pub struct PresigningConfigError {
+ kind: ErrorKind,
+}
- impl std::error::Error for Error {}
+impl std::error::Error for PresigningConfigError {}
- impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self.kind {
- ErrorKind::ExpiresInDurationTooLong => {
- write!(f, "`expires_in` must be no longer than one week")
- }
- ErrorKind::ExpiresInRequired => write!(f, "`expires_in` is required"),
+impl fmt::Display for PresigningConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.kind {
+ ErrorKind::ExpiresInDurationTooLong => {
+ write!(f, "`expires_in` must be no longer than one week")
}
+ ErrorKind::ExpiresInRequired => write!(f, "`expires_in` is required"),
}
}
+}
- impl From for Error {
- fn from(kind: ErrorKind) -> Self {
- Self { kind }
- }
- }
-
- /// Builder used to create `PresigningConfig`.
- #[non_exhaustive]
- #[derive(Default, Debug)]
- pub struct Builder {
- start_time: Option,
- expires_in: Option,
+impl From for PresigningConfigError {
+ fn from(kind: ErrorKind) -> Self {
+ Self { kind }
}
+}
- impl Builder {
- /// Sets the start time for the presigned request.
- ///
- /// The request will start to be valid at this time, and will cease to be valid after
- /// the end time, which can be determined by adding the `expires_in` duration to this
- /// start time. If not specified, this will default to the current time.
- ///
- /// Optional.
- pub fn start_time(mut self, start_time: SystemTime) -> Self {
- self.set_start_time(Some(start_time));
- self
- }
-
- /// Sets the start time for the presigned request.
- ///
- /// The request will start to be valid at this time, and will cease to be valid after
- /// the end time, which can be determined by adding the `expires_in` duration to this
- /// start time. If not specified, this will default to the current time.
- ///
- /// Optional.
- pub fn set_start_time(&mut self, start_time: Option) {
- self.start_time = start_time;
- }
-
- /// Sets how long the request should be valid after the `start_time` (which defaults
- /// to the current time).
- ///
- /// Credential expiration time takes priority over the `expires_in` value.
- /// If the credentials used to sign the request expire before the presigned request is
- /// set to expire, then the presigned request will become invalid.
- ///
- /// Required.
- pub fn expires_in(mut self, expires_in: Duration) -> Self {
- self.set_expires_in(Some(expires_in));
- self
- }
-
- /// Sets how long the request should be valid after the `start_time` (which defaults
- /// to the current time).
- ///
- /// Credential expiration time takes priority over the `expires_in` value.
- /// If the credentials used to sign the request expire before the presigned request is
- /// set to expire, then the presigned request will become invalid.
- ///
- /// Required.
- pub fn set_expires_in(&mut self, expires_in: Option) {
- self.expires_in = expires_in;
- }
+/// Builder used to create `PresigningConfig`.
+#[non_exhaustive]
+#[derive(Default, Debug)]
+pub struct PresigningConfigBuilder {
+ start_time: Option,
+ expires_in: Option,
+}
- /// Builds the `PresigningConfig`. This will error if `expires_in` is not
- /// given, or if it's longer than one week.
- pub fn build(self) -> Result {
- let expires_in = self.expires_in.ok_or(ErrorKind::ExpiresInRequired)?;
- if expires_in > ONE_WEEK {
- return Err(ErrorKind::ExpiresInDurationTooLong.into());
- }
- Ok(PresigningConfig {
- start_time: self.start_time.unwrap_or_else(SystemTime::now),
- expires_in,
- })
- }
+impl PresigningConfigBuilder {
+ /// Sets the start time for the presigned request.
+ ///
+ /// The request will start to be valid at this time, and will cease to be valid after
+ /// the end time, which can be determined by adding the `expires_in` duration to this
+ /// start time. If not specified, this will default to the current time.
+ ///
+ /// Optional.
+ pub fn start_time(mut self, start_time: SystemTime) -> Self {
+ self.set_start_time(Some(start_time));
+ self
}
-}
-/// Presigned request
-pub mod request {
- use std::fmt::{Debug, Formatter};
+ /// Sets the start time for the presigned request.
+ ///
+ /// The request will start to be valid at this time, and will cease to be valid after
+ /// the end time, which can be determined by adding the `expires_in` duration to this
+ /// start time. If not specified, this will default to the current time.
+ ///
+ /// Optional.
+ pub fn set_start_time(&mut self, start_time: Option) {
+ self.start_time = start_time;
+ }
- /// Represents a presigned request. This only includes the HTTP request method, URI, and headers.
+ /// Sets how long the request should be valid after the `start_time` (which defaults
+ /// to the current time).
///
- /// **This struct has conversion convenience functions:**
+ /// Credential expiration time takes priority over the `expires_in` value.
+ /// If the credentials used to sign the request expire before the presigned request is
+ /// set to expire, then the presigned request will become invalid.
///
- /// - [`PresignedRequest::to_http_request`][Self::to_http_request] returns an [`http::Request`](https://docs.rs/http/0.2.6/http/request/struct.Request.html)
- /// - [`PresignedRequest::into`](#impl-From) returns an [`http::request::Builder`](https://docs.rs/http/0.2.6/http/request/struct.Builder.html)
- #[non_exhaustive]
- pub struct PresignedRequest(http::Request<()>);
+ /// Required.
+ pub fn expires_in(mut self, expires_in: Duration) -> Self {
+ self.set_expires_in(Some(expires_in));
+ self
+ }
- impl PresignedRequest {
- pub(crate) fn new(inner: http::Request<()>) -> Self {
- Self(inner)
- }
+ /// Sets how long the request should be valid after the `start_time` (which defaults
+ /// to the current time).
+ ///
+ /// Credential expiration time takes priority over the `expires_in` value.
+ /// If the credentials used to sign the request expire before the presigned request is
+ /// set to expire, then the presigned request will become invalid.
+ ///
+ /// Required.
+ pub fn set_expires_in(&mut self, expires_in: Option) {
+ self.expires_in = expires_in;
+ }
- /// Returns the HTTP request method.
- pub fn method(&self) -> &http::Method {
- self.0.method()
+ /// Builds the `PresigningConfig`. This will error if `expires_in` is not
+ /// given, or if it's longer than one week.
+ pub fn build(self) -> Result {
+ let expires_in = self.expires_in.ok_or(ErrorKind::ExpiresInRequired)?;
+ if expires_in > ONE_WEEK {
+ return Err(ErrorKind::ExpiresInDurationTooLong.into());
}
+ Ok(PresigningConfig {
+ start_time: self.start_time.unwrap_or_else(SystemTime::now),
+ expires_in,
+ })
+ }
+}
- /// Returns the HTTP request URI.
- pub fn uri(&self) -> &http::Uri {
- self.0.uri()
- }
+/// Represents a presigned request. This only includes the HTTP request method, URI, and headers.
+///
+/// **This struct has conversion convenience functions:**
+///
+/// - [`PresignedRequest::to_http_request`][Self::to_http_request] returns an [`http::Request`](https://docs.rs/http/0.2.6/http/request/struct.Request.html)
+/// - [`PresignedRequest::into`](#impl-From) returns an [`http::request::Builder`](https://docs.rs/http/0.2.6/http/request/struct.Builder.html)
+#[non_exhaustive]
+pub struct PresignedRequest(http::Request<()>);
+
+impl PresignedRequest {
+ pub(crate) fn new(inner: http::Request<()>) -> Self {
+ Self(inner)
+ }
- /// Returns any HTTP headers that need to go along with the request, except for `Host`,
- /// which should be sent based on the endpoint in the URI by the HTTP client rather than
- /// added directly.
- pub fn headers(&self) -> &http::HeaderMap {
- self.0.headers()
- }
+ /// Returns the HTTP request method.
+ pub fn method(&self) -> &http::Method {
+ self.0.method()
+ }
- /// Given a body, convert this `PresignedRequest` into an `http::Request`
- pub fn to_http_request(self, body: B) -> Result, http::Error> {
- let builder: http::request::Builder = self.into();
+ /// Returns the HTTP request URI.
+ pub fn uri(&self) -> &http::Uri {
+ self.0.uri()
+ }
- builder.body(body)
- }
+ /// Returns any HTTP headers that need to go along with the request, except for `Host`,
+ /// which should be sent based on the endpoint in the URI by the HTTP client rather than
+ /// added directly.
+ pub fn headers(&self) -> &http::HeaderMap {
+ self.0.headers()
}
- impl Debug for PresignedRequest {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("PresignedRequest")
- .field("method", self.method())
- .field("uri", self.uri())
- .field("headers", self.headers())
- .finish()
- }
+ /// Given a body, convert this `PresignedRequest` into an `http::Request`
+ pub fn to_http_request(self, body: B) -> Result, http::Error> {
+ let builder: http::request::Builder = self.into();
+
+ builder.body(body)
}
+}
- impl From for http::request::Builder {
- fn from(req: PresignedRequest) -> Self {
- let mut builder = http::request::Builder::new()
- .uri(req.uri())
- .method(req.method());
+impl fmt::Debug for PresignedRequest {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("PresignedRequest")
+ .field("method", self.method())
+ .field("uri", self.uri())
+ .field("headers", self.headers())
+ .finish()
+ }
+}
- if let Some(headers) = builder.headers_mut() {
- *headers = req.headers().clone();
- }
+impl From for http::request::Builder {
+ fn from(req: PresignedRequest) -> Self {
+ let mut builder = http::request::Builder::new()
+ .uri(req.uri())
+ .method(req.method());
- builder
+ if let Some(headers) = builder.headers_mut() {
+ *headers = req.headers().clone();
}
+
+ builder
}
}
/// Tower middleware service for creating presigned requests
#[allow(dead_code)]
pub(crate) mod service {
- use crate::presigning::request::PresignedRequest;
+ use super::PresignedRequest;
use aws_smithy_http::operation;
use http::header::USER_AGENT;
use std::future::{ready, Ready};
diff --git a/aws/rust-runtime/aws-inlineable/src/s3_errors.rs b/aws/rust-runtime/aws-inlineable/src/s3_errors.rs
deleted file mode 100644
index ca15ddc42bb..00000000000
--- a/aws/rust-runtime/aws-inlineable/src/s3_errors.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-use http::{HeaderMap, HeaderValue};
-
-const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id";
-
-/// S3-specific service error additions.
-pub trait ErrorExt {
- /// Returns the S3 Extended Request ID necessary when contacting AWS Support.
- /// Read more at .
- fn extended_request_id(&self) -> Option<&str>;
-}
-
-impl ErrorExt for aws_smithy_types::Error {
- fn extended_request_id(&self) -> Option<&str> {
- self.extra(EXTENDED_REQUEST_ID)
- }
-}
-
-/// Parses the S3 Extended Request ID out of S3 error response headers.
-pub fn parse_extended_error(
- error: aws_smithy_types::Error,
- headers: &HeaderMap,
-) -> aws_smithy_types::Error {
- let mut builder = error.into_builder();
- let host_id = headers
- .get("x-amz-id-2")
- .and_then(|header_value| header_value.to_str().ok());
- if let Some(host_id) = host_id {
- builder.custom(EXTENDED_REQUEST_ID, host_id);
- }
- builder.build()
-}
-
-#[cfg(test)]
-mod test {
- use crate::s3_errors::{parse_extended_error, ErrorExt};
-
- #[test]
- fn add_error_fields() {
- let resp = http::Response::builder()
- .header(
- "x-amz-id-2",
- "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2mkiDFu8yU9AS1ed4OpIszj7UDNEHGran",
- )
- .status(400)
- .body("")
- .unwrap();
- let error = aws_smithy_types::Error::builder()
- .message("123")
- .request_id("456")
- .build();
-
- let error = parse_extended_error(error, resp.headers());
- assert_eq!(
- error
- .extended_request_id()
- .expect("extended request id should be set"),
- "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2mkiDFu8yU9AS1ed4OpIszj7UDNEHGran"
- );
- }
-
- #[test]
- fn handle_missing_header() {
- let resp = http::Response::builder().status(400).body("").unwrap();
- let error = aws_smithy_types::Error::builder()
- .message("123")
- .request_id("456")
- .build();
-
- let error = parse_extended_error(error, resp.headers());
- assert_eq!(error.extended_request_id(), None);
- }
-}
diff --git a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs
new file mode 100644
index 00000000000..909dcbcd7aa
--- /dev/null
+++ b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs
@@ -0,0 +1,178 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+use aws_smithy_client::SdkError;
+use aws_smithy_http::http::HttpHeaders;
+use aws_smithy_http::operation;
+use aws_smithy_types::error::metadata::{
+ Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata,
+};
+use aws_smithy_types::error::Unhandled;
+use http::{HeaderMap, HeaderValue};
+
+const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id";
+
+/// Trait to retrieve the S3-specific extended request ID
+///
+/// Read more at .
+pub trait RequestIdExt {
+ /// Returns the S3 Extended Request ID necessary when contacting AWS Support.
+ fn extended_request_id(&self) -> Option<&str>;
+}
+
+impl RequestIdExt for SdkError
+where
+ R: HttpHeaders,
+{
+ fn extended_request_id(&self) -> Option<&str> {
+ match self {
+ Self::ResponseError(err) => extract_extended_request_id(err.raw().http_headers()),
+ Self::ServiceError(err) => extract_extended_request_id(err.raw().http_headers()),
+ _ => None,
+ }
+ }
+}
+
+impl RequestIdExt for ErrorMetadata {
+ fn extended_request_id(&self) -> Option<&str> {
+ self.extra(EXTENDED_REQUEST_ID)
+ }
+}
+
+impl RequestIdExt for Unhandled {
+ fn extended_request_id(&self) -> Option<&str> {
+ self.meta().extended_request_id()
+ }
+}
+
+impl RequestIdExt for operation::Response {
+ fn extended_request_id(&self) -> Option<&str> {
+ extract_extended_request_id(self.http().headers())
+ }
+}
+
+impl RequestIdExt for http::Response {
+ fn extended_request_id(&self) -> Option<&str> {
+ extract_extended_request_id(self.headers())
+ }
+}
+
+impl RequestIdExt for Result
+where
+ O: RequestIdExt,
+ E: RequestIdExt,
+{
+ fn extended_request_id(&self) -> Option<&str> {
+ match self {
+ Ok(ok) => ok.extended_request_id(),
+ Err(err) => err.extended_request_id(),
+ }
+ }
+}
+
+/// Applies the extended request ID to a generic error builder
+#[doc(hidden)]
+pub fn apply_extended_request_id(
+ builder: ErrorMetadataBuilder,
+ headers: &HeaderMap,
+) -> ErrorMetadataBuilder {
+ if let Some(extended_request_id) = extract_extended_request_id(headers) {
+ builder.custom(EXTENDED_REQUEST_ID, extended_request_id)
+ } else {
+ builder
+ }
+}
+
+/// Extracts the S3 Extended Request ID from HTTP response headers
+fn extract_extended_request_id(headers: &HeaderMap) -> Option<&str> {
+ headers
+ .get("x-amz-id-2")
+ .and_then(|value| value.to_str().ok())
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use aws_smithy_client::SdkError;
+ use aws_smithy_http::body::SdkBody;
+ use http::Response;
+
+ #[test]
+ fn handle_missing_header() {
+ let resp = http::Response::builder().status(400).body("").unwrap();
+ let mut builder = aws_smithy_types::Error::builder().message("123");
+ builder = apply_extended_request_id(builder, resp.headers());
+ assert_eq!(builder.build().extended_request_id(), None);
+ }
+
+ #[test]
+ fn test_extended_request_id_sdk_error() {
+ let without_extended_request_id =
+ || operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap());
+ let with_extended_request_id = || {
+ operation::Response::new(
+ Response::builder()
+ .header("x-amz-id-2", HeaderValue::from_static("some-request-id"))
+ .body(SdkBody::empty())
+ .unwrap(),
+ )
+ };
+ assert_eq!(
+ None,
+ SdkError::<(), _>::response_error("test", without_extended_request_id())
+ .extended_request_id()
+ );
+ assert_eq!(
+ Some("some-request-id"),
+ SdkError::<(), _>::response_error("test", with_extended_request_id())
+ .extended_request_id()
+ );
+ assert_eq!(
+ None,
+ SdkError::service_error((), without_extended_request_id()).extended_request_id()
+ );
+ assert_eq!(
+ Some("some-request-id"),
+ SdkError::service_error((), with_extended_request_id()).extended_request_id()
+ );
+ }
+
+ #[test]
+ fn test_extract_extended_request_id() {
+ let mut headers = HeaderMap::new();
+ assert_eq!(None, extract_extended_request_id(&headers));
+
+ headers.append("x-amz-id-2", HeaderValue::from_static("some-request-id"));
+ assert_eq!(
+ Some("some-request-id"),
+ extract_extended_request_id(&headers)
+ );
+ }
+
+ #[test]
+ fn test_apply_extended_request_id() {
+ let mut headers = HeaderMap::new();
+ assert_eq!(
+ ErrorMetadata::builder().build(),
+ apply_extended_request_id(ErrorMetadata::builder(), &headers).build(),
+ );
+
+ headers.append("x-amz-id-2", HeaderValue::from_static("some-request-id"));
+ assert_eq!(
+ ErrorMetadata::builder()
+ .custom(EXTENDED_REQUEST_ID, "some-request-id")
+ .build(),
+ apply_extended_request_id(ErrorMetadata::builder(), &headers).build(),
+ );
+ }
+
+ #[test]
+ fn test_error_metadata_extended_request_id_impl() {
+ let err = ErrorMetadata::builder()
+ .custom(EXTENDED_REQUEST_ID, "some-request-id")
+ .build();
+ assert_eq!(Some("some-request-id"), err.extended_request_id());
+ }
+}
diff --git a/aws/rust-runtime/aws-sig-auth/src/lib.rs b/aws/rust-runtime/aws-sig-auth/src/lib.rs
index 61ae88e81c4..643b3ff216b 100644
--- a/aws/rust-runtime/aws-sig-auth/src/lib.rs
+++ b/aws/rust-runtime/aws-sig-auth/src/lib.rs
@@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+#![allow(clippy::derive_partial_eq_without_eq)]
+
//! AWS Signature Authentication Package
//!
//! This crate may be used to generate presigned URLs for unmodeled behavior such as `rds-iam-token`
diff --git a/aws/rust-runtime/aws-sigv4/Cargo.toml b/aws/rust-runtime/aws-sigv4/Cargo.toml
index ad1570bb863..90cf5f42f30 100644
--- a/aws/rust-runtime/aws-sigv4/Cargo.toml
+++ b/aws/rust-runtime/aws-sigv4/Cargo.toml
@@ -32,7 +32,7 @@ sha2 = "0.10"
criterion = "0.4"
bytes = "1"
httparse = "1.5"
-pretty_assertions = "1.0"
+pretty_assertions = "1.3"
proptest = "1"
time = { version = "0.3.4", features = ["parsing"] }
diff --git a/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs b/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs
index 172f2cbce77..08ac6c8f544 100644
--- a/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs
+++ b/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs
@@ -5,7 +5,6 @@
use crate::date_time::{format_date, format_date_time};
use crate::http_request::error::CanonicalRequestError;
-use crate::http_request::query_writer::QueryWriter;
use crate::http_request::settings::UriPathNormalizationMode;
use crate::http_request::sign::SignableRequest;
use crate::http_request::uri_path_normalization::normalize_uri_path;
@@ -13,6 +12,7 @@ use crate::http_request::url_escape::percent_encode_path;
use crate::http_request::PercentEncodingMode;
use crate::http_request::{PayloadChecksumKind, SignableBody, SignatureLocation, SigningParams};
use crate::sign::sha256_hex_string;
+use aws_smithy_http::query_writer::QueryWriter;
use http::header::{AsHeaderName, HeaderName, HOST};
use http::{HeaderMap, HeaderValue, Method, Uri};
use std::borrow::Cow;
@@ -519,13 +519,13 @@ mod tests {
use crate::http_request::canonical_request::{
normalize_header_value, trim_all, CanonicalRequest, SigningScope, StringToSign,
};
- use crate::http_request::query_writer::QueryWriter;
use crate::http_request::test::{test_canonical_request, test_request, test_sts};
use crate::http_request::{
PayloadChecksumKind, SignableBody, SignableRequest, SigningSettings,
};
use crate::http_request::{SignatureLocation, SigningParams};
use crate::sign::sha256_hex_string;
+ use aws_smithy_http::query_writer::QueryWriter;
use http::Uri;
use http::{header::HeaderName, HeaderValue};
use pretty_assertions::assert_eq;
diff --git a/aws/rust-runtime/aws-sigv4/src/http_request/mod.rs b/aws/rust-runtime/aws-sigv4/src/http_request/mod.rs
index c93a052887c..543d58fb2dd 100644
--- a/aws/rust-runtime/aws-sigv4/src/http_request/mod.rs
+++ b/aws/rust-runtime/aws-sigv4/src/http_request/mod.rs
@@ -43,7 +43,6 @@
mod canonical_request;
mod error;
-mod query_writer;
mod settings;
mod sign;
mod uri_path_normalization;
diff --git a/aws/rust-runtime/aws-sigv4/src/http_request/sign.rs b/aws/rust-runtime/aws-sigv4/src/http_request/sign.rs
index 69f5c00819a..dbe76242947 100644
--- a/aws/rust-runtime/aws-sigv4/src/http_request/sign.rs
+++ b/aws/rust-runtime/aws-sigv4/src/http_request/sign.rs
@@ -8,10 +8,10 @@ use super::{PayloadChecksumKind, SignatureLocation};
use crate::http_request::canonical_request::header;
use crate::http_request::canonical_request::param;
use crate::http_request::canonical_request::{CanonicalRequest, StringToSign, HMAC_256};
-use crate::http_request::query_writer::QueryWriter;
use crate::http_request::SigningParams;
use crate::sign::{calculate_signature, generate_signing_key, sha256_hex_string};
use crate::SigningOutput;
+use aws_smithy_http::query_writer::QueryWriter;
use http::header::HeaderValue;
use http::{HeaderMap, Method, Uri};
use std::borrow::Cow;
@@ -380,9 +380,11 @@ mod tests {
#[test]
fn test_sign_vanilla_with_query_params() {
- let mut settings = SigningSettings::default();
- settings.signature_location = SignatureLocation::QueryParams;
- settings.expires_in = Some(Duration::from_secs(35));
+ let settings = SigningSettings {
+ signature_location: SignatureLocation::QueryParams,
+ expires_in: Some(Duration::from_secs(35)),
+ ..Default::default()
+ };
let params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
diff --git a/aws/rust-runtime/aws-sigv4/src/http_request/url_escape.rs b/aws/rust-runtime/aws-sigv4/src/http_request/url_escape.rs
index 651c62c44ac..d7656c355b2 100644
--- a/aws/rust-runtime/aws-sigv4/src/http_request/url_escape.rs
+++ b/aws/rust-runtime/aws-sigv4/src/http_request/url_escape.rs
@@ -3,11 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-use aws_smithy_http::{label, query};
-
-pub(super) fn percent_encode_query(value: &str) -> String {
- query::fmt_string(value)
-}
+use aws_smithy_http::label;
pub(super) fn percent_encode_path(value: &str) -> String {
label::fmt_string(value, label::EncodingStrategy::Greedy)
diff --git a/aws/rust-runtime/aws-sigv4/src/lib.rs b/aws/rust-runtime/aws-sigv4/src/lib.rs
index be2552f5781..14d7a7b5fd2 100644
--- a/aws/rust-runtime/aws-sigv4/src/lib.rs
+++ b/aws/rust-runtime/aws-sigv4/src/lib.rs
@@ -6,6 +6,7 @@
//! Provides functions for calculating Sigv4 signing keys, signatures, and
//! optional utilities for signing HTTP requests and Event Stream messages.
+#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
diff --git a/aws/rust-runtime/aws-types/src/lib.rs b/aws/rust-runtime/aws-types/src/lib.rs
index 795ff08849b..600adc83cc2 100644
--- a/aws/rust-runtime/aws-types/src/lib.rs
+++ b/aws/rust-runtime/aws-types/src/lib.rs
@@ -5,6 +5,7 @@
//! Cross-service types for the AWS SDK.
+#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt
index 75d6fd7cbdb..3e55fd9ce2e 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt
@@ -9,12 +9,15 @@ import software.amazon.smithy.rust.codegen.client.smithy.customizations.DocsRsMe
import software.amazon.smithy.rust.codegen.client.smithy.customizations.DocsRsMetadataSettings
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator
+import software.amazon.smithy.rustsdk.customize.DisabledAuthDecorator
import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator
-import software.amazon.smithy.rustsdk.customize.auth.DisabledAuthDecorator
+import software.amazon.smithy.rustsdk.customize.applyDecorators
import software.amazon.smithy.rustsdk.customize.ec2.Ec2Decorator
import software.amazon.smithy.rustsdk.customize.glacier.GlacierDecorator
+import software.amazon.smithy.rustsdk.customize.onlyApplyTo
import software.amazon.smithy.rustsdk.customize.route53.Route53Decorator
import software.amazon.smithy.rustsdk.customize.s3.S3Decorator
+import software.amazon.smithy.rustsdk.customize.s3.S3ExtendedRequestIdDecorator
import software.amazon.smithy.rustsdk.customize.s3control.S3ControlDecorator
import software.amazon.smithy.rustsdk.customize.sts.STSDecorator
import software.amazon.smithy.rustsdk.endpoints.AwsEndpointDecorator
@@ -23,41 +26,49 @@ import software.amazon.smithy.rustsdk.endpoints.OperationInputTestDecorator
val DECORATORS: List = listOf(
// General AWS Decorators
- CredentialsCacheDecorator(),
- CredentialsProviderDecorator(),
- RegionDecorator(),
- AwsEndpointDecorator(),
- UserAgentDecorator(),
- SigV4SigningDecorator(),
- HttpRequestChecksumDecorator(),
- HttpResponseChecksumDecorator(),
- RetryClassifierDecorator(),
- IntegrationTestDecorator(),
- AwsFluentClientDecorator(),
- CrateLicenseDecorator(),
- SdkConfigDecorator(),
- ServiceConfigDecorator(),
- AwsPresigningDecorator(),
- AwsReadmeDecorator(),
- HttpConnectorDecorator(),
- AwsEndpointsStdLib(),
- *PromotedBuiltInsDecorators,
- GenericSmithySdkConfigSettings(),
- OperationInputTestDecorator(),
+ listOf(
+ CredentialsCacheDecorator(),
+ CredentialsProviderDecorator(),
+ RegionDecorator(),
+ AwsEndpointDecorator(),
+ UserAgentDecorator(),
+ SigV4SigningDecorator(),
+ HttpRequestChecksumDecorator(),
+ HttpResponseChecksumDecorator(),
+ RetryClassifierDecorator(),
+ IntegrationTestDecorator(),
+ AwsFluentClientDecorator(),
+ CrateLicenseDecorator(),
+ SdkConfigDecorator(),
+ ServiceConfigDecorator(),
+ AwsPresigningDecorator(),
+ AwsReadmeDecorator(),
+ HttpConnectorDecorator(),
+ AwsEndpointsStdLib(),
+ *PromotedBuiltInsDecorators,
+ GenericSmithySdkConfigSettings(),
+ OperationInputTestDecorator(),
+ AwsRequestIdDecorator(),
+ DisabledAuthDecorator(),
+ ),
// Service specific decorators
- ApiGatewayDecorator(),
- DisabledAuthDecorator(),
- Ec2Decorator(),
- GlacierDecorator(),
- Route53Decorator(),
- S3Decorator(),
- S3ControlDecorator(),
- STSDecorator(),
+ ApiGatewayDecorator().onlyApplyTo("com.amazonaws.apigateway#BackplaneControlService"),
+ Ec2Decorator().onlyApplyTo("com.amazonaws.ec2#AmazonEC2"),
+ GlacierDecorator().onlyApplyTo("com.amazonaws.glacier#Glacier"),
+ Route53Decorator().onlyApplyTo("com.amazonaws.route53#AWSDnsV20130401"),
+ "com.amazonaws.s3#AmazonS3".applyDecorators(
+ S3Decorator(),
+ S3ExtendedRequestIdDecorator(),
+ ),
+ S3ControlDecorator().onlyApplyTo("com.amazonaws.s3control#AWSS3ControlServiceV20180820"),
+ STSDecorator().onlyApplyTo("com.amazonaws.sts#AWSSecurityTokenServiceV20110615"),
// Only build docs-rs for linux to reduce load on docs.rs
- DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true)),
-)
+ listOf(
+ DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true)),
+ ),
+).flatten()
class AwsCodegenDecorator : CombinedClientCodegenDecorator(DECORATORS) {
override val name: String = "AwsSdkCodegenDecorator"
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt
index c06b58994bd..e240d642700 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt
@@ -9,14 +9,14 @@ import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.TitleTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
-import software.amazon.smithy.rust.codegen.client.smithy.generators.client.CustomizableOperationGenerator
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedCustomizeModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerics
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientSection
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
-import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope
import software.amazon.smithy.rust.codegen.core.rustlang.Feature
import software.amazon.smithy.rust.codegen.core.rustlang.GenericTypeArg
import software.amazon.smithy.rust.codegen.core.rustlang.RustGenerics
@@ -76,7 +76,7 @@ private class AwsClientGenerics(private val types: Types) : FluentClientGenerics
override fun sendBounds(
operation: Symbol,
operationOutput: Symbol,
- operationError: RuntimeType,
+ operationError: Symbol,
retryClassifier: RuntimeType,
): Writable =
writable { }
@@ -96,17 +96,18 @@ class AwsFluentClientDecorator : ClientCodegenDecorator {
val generics = AwsClientGenerics(types)
FluentClientGenerator(
codegenContext,
- generics,
+ reexportSmithyClientBuilder = false,
+ generics = generics,
customizations = listOf(
- AwsPresignedFluentBuilderMethod(runtimeConfig),
+ AwsPresignedFluentBuilderMethod(codegenContext, runtimeConfig),
AwsFluentClientDocs(codegenContext),
),
retryClassifier = AwsRuntimeType.awsHttp(runtimeConfig).resolve("retry::AwsResponseRetryClassifier"),
).render(rustCrate)
- rustCrate.withModule(CustomizableOperationGenerator.CustomizeModule) {
+ rustCrate.withModule(codegenContext.featureGatedCustomizeModule()) {
renderCustomizableOperationSendMethod(runtimeConfig, generics, this)
}
- rustCrate.withModule(FluentClientGenerator.clientModule) {
+ rustCrate.withModule(ClientRustModule.client) {
AwsFluentClientExtensions(types).render(this)
}
val awsSmithyClient = "aws-smithy-client"
@@ -228,7 +229,7 @@ private class AwsFluentClientDocs(private val codegenContext: CodegenContext) :
private val serviceShape = codegenContext.serviceShape
private val crateName = codegenContext.moduleUseName()
private val codegenScope =
- arrayOf("aws_config" to AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).copy(scope = DependencyScope.Dev).toType())
+ arrayOf("aws_config" to AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).toDevDependency().toType())
// If no `aws-config` version is provided, assume that docs referencing `aws-config` cannot be given.
// Also, STS and SSO must NOT reference `aws-config` since that would create a circular dependency.
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt
index c733138076d..1ecca4651e4 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsPresigningDecorator.kt
@@ -30,12 +30,10 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.withBlock
import software.amazon.smithy.rust.codegen.core.rustlang.writable
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpBoundProtocolPayloadGenerator
import software.amazon.smithy.rust.codegen.core.util.cloneOperation
import software.amazon.smithy.rust.codegen.core.util.expectTrait
@@ -129,23 +127,23 @@ class AwsPresigningDecorator internal constructor(
}
class AwsInputPresignedMethod(
- private val codegenContext: CodegenContext,
+ private val codegenContext: ClientCodegenContext,
private val operationShape: OperationShape,
) : OperationCustomization() {
private val runtimeConfig = codegenContext.runtimeConfig
private val symbolProvider = codegenContext.symbolProvider
- private val codegenScope = arrayOf(
- "Error" to AwsRuntimeType.Presigning.resolve("config::Error"),
- "PresignedRequest" to AwsRuntimeType.Presigning.resolve("request::PresignedRequest"),
- "PresignedRequestService" to AwsRuntimeType.Presigning.resolve("service::PresignedRequestService"),
- "PresigningConfig" to AwsRuntimeType.Presigning.resolve("config::PresigningConfig"),
- "SdkError" to RuntimeType.sdkError(runtimeConfig),
- "aws_sigv4" to AwsRuntimeType.awsSigv4(runtimeConfig),
- "sig_auth" to AwsRuntimeType.awsSigAuth(runtimeConfig),
- "tower" to RuntimeType.Tower,
- "Middleware" to runtimeConfig.defaultMiddleware(),
- )
+ private val codegenScope = (
+ presigningTypes(codegenContext) + listOf(
+ "PresignedRequestService" to AwsRuntimeType.presigning(codegenContext)
+ .resolve("service::PresignedRequestService"),
+ "SdkError" to RuntimeType.sdkError(runtimeConfig),
+ "aws_sigv4" to AwsRuntimeType.awsSigv4(runtimeConfig),
+ "sig_auth" to AwsRuntimeType.awsSigAuth(runtimeConfig),
+ "tower" to RuntimeType.Tower,
+ "Middleware" to runtimeConfig.defaultMiddleware(),
+ )
+ ).toTypedArray()
override fun section(section: OperationSection): Writable =
writable {
@@ -155,7 +153,7 @@ class AwsInputPresignedMethod(
}
private fun RustWriter.writeInputPresignedMethod(section: OperationSection.InputImpl) {
- val operationError = operationShape.errorSymbol(symbolProvider)
+ val operationError = symbolProvider.symbolForOperationError(operationShape)
val presignableOp = PRESIGNABLE_OPERATIONS.getValue(operationShape.id)
val makeOperationOp = if (presignableOp.hasModelTransforms()) {
@@ -242,14 +240,15 @@ class AwsInputPresignedMethod(
}
class AwsPresignedFluentBuilderMethod(
+ codegenContext: ClientCodegenContext,
runtimeConfig: RuntimeConfig,
) : FluentClientCustomization() {
- private val codegenScope = arrayOf(
- "Error" to AwsRuntimeType.Presigning.resolve("config::Error"),
- "PresignedRequest" to AwsRuntimeType.Presigning.resolve("request::PresignedRequest"),
- "PresigningConfig" to AwsRuntimeType.Presigning.resolve("config::PresigningConfig"),
- "SdkError" to RuntimeType.sdkError(runtimeConfig),
- )
+ private val codegenScope = (
+ presigningTypes(codegenContext) + arrayOf(
+ "Error" to AwsRuntimeType.presigning(codegenContext).resolve("config::Error"),
+ "SdkError" to RuntimeType.sdkError(runtimeConfig),
+ )
+ ).toTypedArray()
override fun section(section: FluentClientSection): Writable =
writable {
@@ -365,3 +364,15 @@ private fun RustWriter.documentPresignedMethod(hasConfigArg: Boolean) {
""",
)
}
+
+private fun presigningTypes(codegenContext: ClientCodegenContext): List> =
+ when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> listOf(
+ "PresignedRequest" to AwsRuntimeType.presigning(codegenContext).resolve("PresignedRequest"),
+ "PresigningConfig" to AwsRuntimeType.presigning(codegenContext).resolve("PresigningConfig"),
+ )
+ else -> listOf(
+ "PresignedRequest" to AwsRuntimeType.presigning(codegenContext).resolve("request::PresignedRequest"),
+ "PresigningConfig" to AwsRuntimeType.presigning(codegenContext).resolve("config::PresigningConfig"),
+ )
+ }
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRequestIdDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRequestIdDecorator.kt
new file mode 100644
index 00000000000..0b496ce2c9b
--- /dev/null
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRequestIdDecorator.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rustsdk
+
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+
+/**
+ * Customizes response parsing logic to add AWS request IDs to error metadata and outputs
+ */
+class AwsRequestIdDecorator : BaseRequestIdDecorator() {
+ override val name: String = "AwsRequestIdDecorator"
+ override val order: Byte = 0
+
+ override val fieldName: String = "request_id"
+ override val accessorFunctionName: String = "request_id"
+
+ private fun requestIdModule(codegenContext: ClientCodegenContext): RuntimeType =
+ AwsRuntimeType.awsHttp(codegenContext.runtimeConfig).resolve("request_id")
+
+ override fun accessorTrait(codegenContext: ClientCodegenContext): RuntimeType =
+ requestIdModule(codegenContext).resolve("RequestId")
+
+ override fun applyToError(codegenContext: ClientCodegenContext): RuntimeType =
+ requestIdModule(codegenContext).resolve("apply_request_id")
+}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt
index f4b05b7bafd..e07607a7224 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt
@@ -6,8 +6,8 @@
package software.amazon.smithy.rustsdk
import software.amazon.smithy.codegen.core.CodegenException
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
-import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeCrateLocation
@@ -42,10 +42,17 @@ fun RuntimeConfig.awsRoot(): RuntimeCrateLocation {
}
object AwsRuntimeType {
- val S3Errors by lazy { RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("s3_errors")) }
- val Presigning by lazy {
- RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("presigning", visibility = Visibility.PUBLIC))
- }
+ fun presigning(codegenContext: ClientCodegenContext): RuntimeType =
+ when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("presigning", visibility = Visibility.PUBLIC))
+ else -> RuntimeType.forInlineDependency(
+ InlineAwsDependency.forRustFileAs(
+ file = "old_presigning",
+ moduleName = "presigning",
+ visibility = Visibility.PUBLIC,
+ ),
+ )
+ }
fun RuntimeConfig.defaultMiddleware() = RuntimeType.forInlineDependency(
InlineAwsDependency.forRustFile(
@@ -63,7 +70,7 @@ object AwsRuntimeType {
fun awsCredentialTypes(runtimeConfig: RuntimeConfig) = AwsCargoDependency.awsCredentialTypes(runtimeConfig).toType()
fun awsCredentialTypesTestUtil(runtimeConfig: RuntimeConfig) =
- AwsCargoDependency.awsCredentialTypes(runtimeConfig).copy(scope = DependencyScope.Dev).withFeature("test-util").toType()
+ AwsCargoDependency.awsCredentialTypes(runtimeConfig).toDevDependency().withFeature("test-util").toType()
fun awsEndpoint(runtimeConfig: RuntimeConfig) = AwsCargoDependency.awsEndpoint(runtimeConfig).toType()
fun awsHttp(runtimeConfig: RuntimeConfig) = AwsCargoDependency.awsHttp(runtimeConfig).toType()
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/BaseRequestIdDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/BaseRequestIdDecorator.kt
new file mode 100644
index 00000000000..b70bf419bb0
--- /dev/null
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/BaseRequestIdDecorator.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rustsdk
+
+import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
+import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorSection
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.rustlang.writable
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
+import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderSection
+import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureSection
+import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplSection
+import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait
+import software.amazon.smithy.rust.codegen.core.util.hasTrait
+
+/**
+ * Base customization for adding a request ID (or extended request ID) to outputs and errors.
+ */
+abstract class BaseRequestIdDecorator : ClientCodegenDecorator {
+ abstract val accessorFunctionName: String
+ abstract val fieldName: String
+ abstract fun accessorTrait(codegenContext: ClientCodegenContext): RuntimeType
+ abstract fun applyToError(codegenContext: ClientCodegenContext): RuntimeType
+
+ override fun operationCustomizations(
+ codegenContext: ClientCodegenContext,
+ operation: OperationShape,
+ baseCustomizations: List,
+ ): List = baseCustomizations + listOf(RequestIdOperationCustomization(codegenContext))
+
+ override fun errorCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List =
+ baseCustomizations + listOf(RequestIdErrorCustomization(codegenContext))
+
+ override fun errorImplCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations + listOf(RequestIdErrorImplCustomization(codegenContext))
+
+ override fun structureCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations + listOf(RequestIdStructureCustomization(codegenContext))
+
+ override fun builderCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations + listOf(RequestIdBuilderCustomization())
+
+ override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ rustCrate.withModule(
+ when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> ClientRustModule.Operation
+ else -> ClientRustModule.types
+ },
+ ) {
+ // Re-export RequestId in generated crate
+ rust("pub use #T;", accessorTrait(codegenContext))
+ }
+ }
+
+ private inner class RequestIdOperationCustomization(private val codegenContext: ClientCodegenContext) :
+ OperationCustomization() {
+ override fun section(section: OperationSection): Writable = writable {
+ when (section) {
+ is OperationSection.PopulateErrorMetadataExtras -> {
+ rustTemplate(
+ "${section.builderName} = #{apply_to_error}(${section.builderName}, ${section.responseName}.headers());",
+ "apply_to_error" to applyToError(codegenContext),
+ )
+ }
+ is OperationSection.MutateOutput -> {
+ rust(
+ "output._set_$fieldName(#T::$accessorFunctionName(response).map(str::to_string));",
+ accessorTrait(codegenContext),
+ )
+ }
+ is OperationSection.BeforeParseResponse -> {
+ rustTemplate(
+ "#{tracing}::debug!($fieldName = ?#{trait}::$accessorFunctionName(${section.responseName}));",
+ "tracing" to RuntimeType.Tracing,
+ "trait" to accessorTrait(codegenContext),
+ )
+ }
+ else -> {}
+ }
+ }
+ }
+
+ private inner class RequestIdErrorCustomization(private val codegenContext: ClientCodegenContext) :
+ ErrorCustomization() {
+ override fun section(section: ErrorSection): Writable = writable {
+ when (section) {
+ is ErrorSection.OperationErrorAdditionalTraitImpls -> {
+ rustTemplate(
+ """
+ impl #{AccessorTrait} for #{error} {
+ fn $accessorFunctionName(&self) -> Option<&str> {
+ self.meta().$accessorFunctionName()
+ }
+ }
+ """,
+ "AccessorTrait" to accessorTrait(codegenContext),
+ "error" to section.errorSymbol,
+ )
+ }
+
+ is ErrorSection.ServiceErrorAdditionalTraitImpls -> {
+ rustBlock("impl #T for Error", accessorTrait(codegenContext)) {
+ rustBlock("fn $accessorFunctionName(&self) -> Option<&str>") {
+ rustBlock("match self") {
+ section.allErrors.forEach { error ->
+ val sym = codegenContext.symbolProvider.toSymbol(error)
+ rust("Self::${sym.name}(e) => e.$accessorFunctionName(),")
+ }
+ rust("Self::Unhandled(e) => e.$accessorFunctionName(),")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private inner class RequestIdErrorImplCustomization(private val codegenContext: ClientCodegenContext) :
+ ErrorImplCustomization() {
+ override fun section(section: ErrorImplSection): Writable = writable {
+ when (section) {
+ is ErrorImplSection.ErrorAdditionalTraitImpls -> {
+ rustBlock("impl #1T for #2T", accessorTrait(codegenContext), section.errorType) {
+ rustBlock("fn $accessorFunctionName(&self) -> Option<&str>") {
+ rust("use #T;", RuntimeType.provideErrorMetadataTrait(codegenContext.runtimeConfig))
+ rust("self.meta().$accessorFunctionName()")
+ }
+ }
+ }
+
+ else -> {}
+ }
+ }
+ }
+
+ private inner class RequestIdStructureCustomization(private val codegenContext: ClientCodegenContext) :
+ StructureCustomization() {
+ override fun section(section: StructureSection): Writable = writable {
+ if (section.shape.hasTrait()) {
+ when (section) {
+ is StructureSection.AdditionalFields -> {
+ rust("_$fieldName: Option,")
+ }
+
+ is StructureSection.AdditionalTraitImpls -> {
+ rustTemplate(
+ """
+ impl #{AccessorTrait} for ${section.structName} {
+ fn $accessorFunctionName(&self) -> Option<&str> {
+ self._$fieldName.as_deref()
+ }
+ }
+ """,
+ "AccessorTrait" to accessorTrait(codegenContext),
+ )
+ }
+
+ is StructureSection.AdditionalDebugFields -> {
+ rust("""${section.formatterName}.field("_$fieldName", &self._$fieldName);""")
+ }
+ }
+ }
+ }
+ }
+
+ private inner class RequestIdBuilderCustomization : BuilderCustomization() {
+ override fun section(section: BuilderSection): Writable = writable {
+ if (section.shape.hasTrait()) {
+ when (section) {
+ is BuilderSection.AdditionalFields -> {
+ rust("_$fieldName: Option,")
+ }
+
+ is BuilderSection.AdditionalMethods -> {
+ rust(
+ """
+ pub(crate) fn _$fieldName(mut self, $fieldName: impl Into) -> Self {
+ self._$fieldName = Some($fieldName.into());
+ self
+ }
+
+ pub(crate) fn _set_$fieldName(&mut self, $fieldName: Option) -> &mut Self {
+ self._$fieldName = $fieldName;
+ self
+ }
+ """,
+ )
+ }
+
+ is BuilderSection.AdditionalDebugFields -> {
+ rust("""${section.formatterName}.field("_$fieldName", &self._$fieldName);""")
+ }
+
+ is BuilderSection.AdditionalFieldsInBuild -> {
+ rust("_$fieldName: self._$fieldName,")
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt
index c9499c39203..30e15a0ca93 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialProviders.kt
@@ -8,9 +8,9 @@ package software.amazon.smithy.rustsdk
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.customize.TestUtilFeature
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedConfigModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
-import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
@@ -19,8 +19,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.adhocCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
class CredentialsProviderDecorator : ClientCodegenDecorator {
override val name: String = "CredentialsProvider"
@@ -33,13 +31,6 @@ class CredentialsProviderDecorator : ClientCodegenDecorator {
return baseCustomizations + CredentialProviderConfig(codegenContext.runtimeConfig)
}
- override fun libRsCustomizations(
- codegenContext: ClientCodegenContext,
- baseCustomizations: List,
- ): List {
- return baseCustomizations + PubUseCredentials(codegenContext.runtimeConfig)
- }
-
override fun extraSections(codegenContext: ClientCodegenContext): List =
listOf(
adhocCustomization { section ->
@@ -49,6 +40,13 @@ class CredentialsProviderDecorator : ClientCodegenDecorator {
override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
rustCrate.mergeFeature(TestUtilFeature.copy(deps = listOf("aws-credential-types/test-util")))
+
+ rustCrate.withModule(codegenContext.featureGatedConfigModule()) {
+ rust(
+ "pub use #T::Credentials;",
+ AwsRuntimeType.awsCredentialTypes(codegenContext.runtimeConfig),
+ )
+ }
}
}
@@ -95,20 +93,5 @@ class CredentialProviderConfig(runtimeConfig: RuntimeConfig) : ConfigCustomizati
}
}
-class PubUseCredentials(private val runtimeConfig: RuntimeConfig) : LibRsCustomization() {
- override fun section(section: LibRsSection): Writable {
- return when (section) {
- is LibRsSection.Body -> writable {
- rust(
- "pub use #T::Credentials;",
- AwsRuntimeType.awsCredentialTypes(runtimeConfig),
- )
- }
-
- else -> emptySection
- }
- }
-}
-
fun defaultProvider() =
RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("no_credentials")).resolve("NoCredentials")
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/HttpRequestChecksumDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/HttpRequestChecksumDecorator.kt
index 0a5a4cfd027..c1799c8bf54 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/HttpRequestChecksumDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/HttpRequestChecksumDecorator.kt
@@ -26,7 +26,7 @@ import software.amazon.smithy.rust.codegen.core.util.orNull
fun RuntimeConfig.awsInlineableBodyWithChecksum() = RuntimeType.forInlineDependency(
InlineAwsDependency.forRustFile(
- "http_body_checksum", visibility = Visibility.PUBLIC,
+ "http_body_checksum", visibility = Visibility.PUBCRATE,
CargoDependency.Http,
CargoDependency.HttpBody,
CargoDependency.smithyHttp(this),
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineAwsDependency.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineAwsDependency.kt
index fa2554655a0..b127795fc33 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineAwsDependency.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/InlineAwsDependency.kt
@@ -12,5 +12,8 @@ import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
object InlineAwsDependency {
fun forRustFile(file: String, visibility: Visibility = Visibility.PRIVATE, vararg additionalDependency: RustDependency): InlineDependency =
- InlineDependency.Companion.forRustFile(RustModule.new(file, visibility), "/aws-inlineable/src/$file.rs", *additionalDependency)
+ forRustFileAs(file, file, visibility, *additionalDependency)
+
+ fun forRustFileAs(file: String, moduleName: String, visibility: Visibility = Visibility.PRIVATE, vararg additionalDependency: RustDependency): InlineDependency =
+ InlineDependency.Companion.forRustFile(RustModule.new(moduleName, visibility), "/aws-inlineable/src/$file.rs", *additionalDependency)
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt
index 0eafec8fcdb..9cbddde2502 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt
@@ -30,6 +30,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
+import software.amazon.smithy.rust.codegen.core.testutil.testDependenciesOnly
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.absolute
@@ -72,7 +73,7 @@ class IntegrationTestDependencies(
private val hasBenches: Boolean,
) : LibRsCustomization() {
override fun section(section: LibRsSection) = when (section) {
- is LibRsSection.Body -> writable {
+ is LibRsSection.Body -> testDependenciesOnly {
if (hasTests) {
val smithyClient = CargoDependency.smithyClient(runtimeConfig)
.copy(features = setOf("test-util"), scope = DependencyScope.Dev)
@@ -81,7 +82,7 @@ class IntegrationTestDependencies(
addDependency(SerdeJson)
addDependency(Tokio)
addDependency(FuturesUtil)
- addDependency(Tracing)
+ addDependency(Tracing.toDevDependency())
addDependency(TracingSubscriber)
}
if (hasBenches) {
@@ -91,6 +92,7 @@ class IntegrationTestDependencies(
serviceSpecific.section(section)(this)
}
}
+
else -> emptySection
}
@@ -114,8 +116,8 @@ class S3TestDependencies : LibRsCustomization() {
override fun section(section: LibRsSection): Writable =
writable {
addDependency(AsyncStd)
- addDependency(BytesUtils)
- addDependency(FastRand)
+ addDependency(BytesUtils.toDevDependency())
+ addDependency(FastRand.toDevDependency())
addDependency(HdrHistogram)
addDependency(Smol)
addDependency(TempFile)
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt
index 70775e2cda4..af625e7979d 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt
@@ -12,6 +12,7 @@ import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedConfigModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
@@ -20,12 +21,11 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
import software.amazon.smithy.rust.codegen.core.smithy.customize.adhocCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.core.util.dq
import software.amazon.smithy.rust.codegen.core.util.extendIf
import software.amazon.smithy.rust.codegen.core.util.thenSingletonListOf
@@ -101,13 +101,6 @@ class RegionDecorator : ClientCodegenDecorator {
return baseCustomizations.extendIf(usesRegion(codegenContext)) { RegionConfigPlugin() }
}
- override fun libRsCustomizations(
- codegenContext: ClientCodegenContext,
- baseCustomizations: List,
- ): List {
- return baseCustomizations.extendIf(usesRegion(codegenContext)) { PubUseRegion(codegenContext.runtimeConfig) }
- }
-
override fun extraSections(codegenContext: ClientCodegenContext): List {
return usesRegion(codegenContext).thenSingletonListOf {
adhocCustomization { section ->
@@ -121,6 +114,14 @@ class RegionDecorator : ClientCodegenDecorator {
}
}
+ override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ if (usesRegion(codegenContext)) {
+ rustCrate.withModule(codegenContext.featureGatedConfigModule()) {
+ rust("pub use #T::Region;", region(codegenContext.runtimeConfig))
+ }
+ }
+ }
+
override fun endpointCustomizations(codegenContext: ClientCodegenContext): List {
if (!usesRegion(codegenContext)) {
return listOf()
@@ -129,7 +130,9 @@ class RegionDecorator : ClientCodegenDecorator {
object : EndpointCustomization {
override fun loadBuiltInFromServiceConfig(parameter: Parameter, configRef: String): Writable? {
return when (parameter.builtIn) {
- Builtins.REGION.builtIn -> writable { rust("$configRef.region.as_ref().map(|r|r.as_ref().to_owned())") }
+ Builtins.REGION.builtIn -> writable {
+ rust("$configRef.region.as_ref().map(|r|r.as_ref().to_owned())")
+ }
else -> null
}
}
@@ -221,19 +224,4 @@ class RegionConfigPlugin : OperationCustomization() {
}
}
-class PubUseRegion(private val runtimeConfig: RuntimeConfig) : LibRsCustomization() {
- override fun section(section: LibRsSection): Writable {
- return when (section) {
- is LibRsSection.Body -> writable {
- rust(
- "pub use #T::Region;",
- region(runtimeConfig),
- )
- }
-
- else -> emptySection
- }
- }
-}
-
fun region(runtimeConfig: RuntimeConfig) = AwsRuntimeType.awsTypes(runtimeConfig).resolve("region")
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt
index f0e2a7e7d64..1d94cd6082b 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt
@@ -6,10 +6,10 @@
package software.amazon.smithy.rustsdk
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
@@ -105,7 +105,7 @@ class SdkConfigDecorator : ClientCodegenDecorator {
val codegenScope = arrayOf(
"SdkConfig" to AwsRuntimeType.awsTypes(codegenContext.runtimeConfig).resolve("sdk_config::SdkConfig"),
)
- rustCrate.withModule(RustModule.Config) {
+ rustCrate.withModule(ClientRustModule.Config) {
rustTemplate(
"""
impl From<{SdkConfig}> for Builder {
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/UserAgentDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/UserAgentDecorator.kt
index 8a2a27f66fb..0fa2c5f66b9 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/UserAgentDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/UserAgentDecorator.kt
@@ -9,6 +9,8 @@ import software.amazon.smithy.aws.traits.ServiceTrait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedConfigModule
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedMetaModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
@@ -16,12 +18,12 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.smithy.customizations.CrateVersionCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
import software.amazon.smithy.rust.codegen.core.smithy.customize.adhocCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.core.util.dq
import software.amazon.smithy.rust.codegen.core.util.expectTrait
@@ -39,21 +41,12 @@ class UserAgentDecorator : ClientCodegenDecorator {
return baseCustomizations + AppNameCustomization(codegenContext.runtimeConfig)
}
- override fun libRsCustomizations(
- codegenContext: ClientCodegenContext,
- baseCustomizations: List,
- ): List {
- // We are generating an AWS SDK, the service needs to have the AWS service trait
- val serviceTrait = codegenContext.serviceShape.expectTrait()
- return baseCustomizations + ApiVersionAndPubUse(codegenContext.runtimeConfig, serviceTrait)
- }
-
override fun operationCustomizations(
codegenContext: ClientCodegenContext,
operation: OperationShape,
baseCustomizations: List,
): List {
- return baseCustomizations + UserAgentFeature(codegenContext.runtimeConfig)
+ return baseCustomizations + UserAgentFeature(codegenContext)
}
override fun extraSections(codegenContext: ClientCodegenContext): List {
@@ -65,44 +58,54 @@ class UserAgentDecorator : ClientCodegenDecorator {
}
/**
- * Adds a static `API_METADATA` variable to the crate root containing the serviceId & the version of the crate for this individual service
+ * Adds a static `API_METADATA` variable to the crate `config` containing the serviceId & the version of the crate for this individual service
*/
- private class ApiVersionAndPubUse(private val runtimeConfig: RuntimeConfig, serviceTrait: ServiceTrait) :
- LibRsCustomization() {
- private val serviceId = serviceTrait.sdkId.lowercase().replace(" ", "")
- override fun section(section: LibRsSection): Writable = when (section) {
- is LibRsSection.Body -> writable {
- // PKG_VERSION comes from CrateVersionGenerator
- rust(
- "static API_METADATA: #1T::ApiMetadata = #1T::ApiMetadata::new(${serviceId.dq()}, PKG_VERSION);",
- AwsRuntimeType.awsHttp(runtimeConfig).resolve("user_agent"),
- )
+ override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ val runtimeConfig = codegenContext.runtimeConfig
- // Re-export the app name so that it can be specified in config programmatically without an explicit dependency
- rustTemplate(
- "pub use #{AppName};",
- "AppName" to AwsRuntimeType.awsTypes(runtimeConfig).resolve("app_name::AppName"),
- )
- }
+ // We are generating an AWS SDK, the service needs to have the AWS service trait
+ val serviceTrait = codegenContext.serviceShape.expectTrait()
+ val serviceId = serviceTrait.sdkId.lowercase().replace(" ", "")
+
+ rustCrate.withModule(codegenContext.featureGatedMetaModule()) {
+ rustTemplate(
+ """
+ pub(crate) static API_METADATA: #{user_agent}::ApiMetadata =
+ #{user_agent}::ApiMetadata::new(${serviceId.dq()}, #{PKG_VERSION});
+ """,
+ "user_agent" to AwsRuntimeType.awsHttp(runtimeConfig).resolve("user_agent"),
+ "PKG_VERSION" to CrateVersionCustomization.pkgVersion(codegenContext.featureGatedMetaModule()),
+ )
+ }
- else -> emptySection
+ rustCrate.withModule(codegenContext.featureGatedConfigModule()) {
+ // Re-export the app name so that it can be specified in config programmatically without an explicit dependency
+ rustTemplate(
+ "pub use #{AppName};",
+ "AppName" to AwsRuntimeType.awsTypes(runtimeConfig).resolve("app_name::AppName"),
+ )
}
}
- private class UserAgentFeature(private val runtimeConfig: RuntimeConfig) : OperationCustomization() {
+ private class UserAgentFeature(
+ private val codegenContext: ClientCodegenContext,
+ ) : OperationCustomization() {
+ private val runtimeConfig = codegenContext.runtimeConfig
+
override fun section(section: OperationSection): Writable = when (section) {
is OperationSection.MutateRequest -> writable {
rustTemplate(
"""
let mut user_agent = #{ua_module}::AwsUserAgent::new_from_environment(
#{Env}::real(),
- crate::API_METADATA.clone(),
+ #{meta}::API_METADATA.clone(),
);
if let Some(app_name) = _config.app_name() {
user_agent = user_agent.with_app_name(app_name.clone());
}
${section.request}.properties_mut().insert(user_agent);
""",
+ "meta" to codegenContext.featureGatedMetaModule(),
"ua_module" to AwsRuntimeType.awsHttp(runtimeConfig).resolve("user_agent"),
"Env" to AwsRuntimeType.awsTypes(runtimeConfig).resolve("os_shim_internal::Env"),
)
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/auth/DisabledAuthDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/DisabledAuthDecorator.kt
similarity index 91%
rename from aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/auth/DisabledAuthDecorator.kt
rename to aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/DisabledAuthDecorator.kt
index 2c65f95bd38..dfbd2ca5979 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/auth/DisabledAuthDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/DisabledAuthDecorator.kt
@@ -3,17 +3,15 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package software.amazon.smithy.rustsdk.customize.auth
+package software.amazon.smithy.rustsdk.customize
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
-import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.AuthTrait
import software.amazon.smithy.model.transform.ModelTransformer
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
-
-private fun String.shapeId() = ShapeId.from(this)
+import software.amazon.smithy.rust.codegen.core.util.shapeId
// / STS (and possibly other services) need to have auth manually set to []
class DisabledAuthDecorator : ClientCodegenDecorator {
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt
new file mode 100644
index 00000000000..8e957b3f599
--- /dev/null
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rustsdk.customize
+
+import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.model.shapes.ServiceShape
+import software.amazon.smithy.model.shapes.ShapeId
+import software.amazon.smithy.model.shapes.ToShapeId
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientProtocolMap
+import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.ManifestCustomizations
+import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization
+
+/** Only apply this decorator to the given service ID */
+fun ClientCodegenDecorator.onlyApplyTo(serviceId: String): List =
+ listOf(ServiceSpecificDecorator(ShapeId.from(serviceId), this))
+
+/** Apply the given decorators only to this service ID */
+fun String.applyDecorators(vararg decorators: ClientCodegenDecorator): List =
+ decorators.map { it.onlyApplyTo(this) }.flatten()
+
+/**
+ * Delegating decorator that only applies to a configured service ID
+ */
+class ServiceSpecificDecorator(
+ /** Service ID this decorator is active for */
+ private val appliesToServiceId: ShapeId,
+ /** Decorator to delegate to */
+ private val delegateTo: ClientCodegenDecorator,
+ /** Decorator name */
+ override val name: String = "${appliesToServiceId.namespace}.${appliesToServiceId.name}",
+ /** Decorator order */
+ override val order: Byte = 0,
+) : ClientCodegenDecorator {
+ private fun T.maybeApply(serviceId: ToShapeId, delegatedValue: () -> T): T =
+ if (appliesToServiceId == serviceId.toShapeId()) {
+ delegatedValue()
+ } else {
+ this
+ }
+
+ // This kind of decorator gets explicitly added to the root sdk-codegen decorator
+ override fun classpathDiscoverable(): Boolean = false
+
+ override fun builderCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.builderCustomizations(codegenContext, baseCustomizations)
+ }
+
+ override fun configCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.configCustomizations(codegenContext, baseCustomizations)
+ }
+
+ override fun crateManifestCustomizations(codegenContext: ClientCodegenContext): ManifestCustomizations =
+ emptyMap().maybeApply(codegenContext.serviceShape) {
+ delegateTo.crateManifestCustomizations(codegenContext)
+ }
+
+ override fun endpointCustomizations(codegenContext: ClientCodegenContext): List =
+ emptyList().maybeApply(codegenContext.serviceShape) {
+ delegateTo.endpointCustomizations(codegenContext)
+ }
+
+ override fun errorCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.errorCustomizations(codegenContext, baseCustomizations)
+ }
+
+ override fun errorImplCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.errorImplCustomizations(codegenContext, baseCustomizations)
+ }
+
+ override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ maybeApply(codegenContext.serviceShape) {
+ delegateTo.extras(codegenContext, rustCrate)
+ }
+ }
+
+ override fun libRsCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.libRsCustomizations(codegenContext, baseCustomizations)
+ }
+
+ override fun operationCustomizations(
+ codegenContext: ClientCodegenContext,
+ operation: OperationShape,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.operationCustomizations(codegenContext, operation, baseCustomizations)
+ }
+
+ override fun protocols(serviceId: ShapeId, currentProtocols: ClientProtocolMap): ClientProtocolMap =
+ currentProtocols.maybeApply(serviceId) {
+ delegateTo.protocols(serviceId, currentProtocols)
+ }
+
+ override fun structureCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) {
+ delegateTo.structureCustomizations(codegenContext, baseCustomizations)
+ }
+
+ override fun transformModel(service: ServiceShape, model: Model): Model =
+ model.maybeApply(service) {
+ delegateTo.transformModel(service, model)
+ }
+}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt
index 9fc0f3e4c70..5959918ef75 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/apigateway/ApiGatewayDecorator.kt
@@ -6,34 +6,24 @@
package software.amazon.smithy.rustsdk.customize.apigateway
import software.amazon.smithy.model.shapes.OperationShape
-import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.writable
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
-import software.amazon.smithy.rust.codegen.core.util.letIf
class ApiGatewayDecorator : ClientCodegenDecorator {
override val name: String = "ApiGateway"
override val order: Byte = 0
- private fun applies(codegenContext: CodegenContext) =
- codegenContext.serviceShape.id == ShapeId.from("com.amazonaws.apigateway#BackplaneControlService")
-
override fun operationCustomizations(
codegenContext: ClientCodegenContext,
operation: OperationShape,
baseCustomizations: List,
- ): List {
- return baseCustomizations.letIf(applies(codegenContext)) {
- it + ApiGatewayAddAcceptHeader()
- }
- }
+ ): List = baseCustomizations + ApiGatewayAddAcceptHeader()
}
class ApiGatewayAddAcceptHeader : OperationCustomization() {
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt
index ee005da318a..e788920e1d1 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ec2/Ec2Decorator.kt
@@ -7,24 +7,14 @@ package software.amazon.smithy.rustsdk.customize.ec2
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
-import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
-import software.amazon.smithy.rust.codegen.core.util.letIf
class Ec2Decorator : ClientCodegenDecorator {
override val name: String = "Ec2"
override val order: Byte = 0
- private val ec2 = ShapeId.from("com.amazonaws.ec2#AmazonEC2")
- private fun applies(serviceShape: ServiceShape) =
- serviceShape.id == ec2
-
- override fun transformModel(service: ServiceShape, model: Model): Model {
- // EC2 incorrectly models primitive shapes as unboxed when they actually
- // need to be boxed for the API to work properly
- return model.letIf(
- applies(service),
- EC2MakePrimitivesOptional::processModel,
- )
- }
+ // EC2 incorrectly models primitive shapes as unboxed when they actually
+ // need to be boxed for the API to work properly
+ override fun transformModel(service: ServiceShape, model: Model): Model =
+ EC2MakePrimitivesOptional.processModel(model)
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt
index 65e4f3a4558..63ef01d1b18 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt
@@ -37,7 +37,9 @@ class AccountIdAutofill : OperationCustomization() {
val input = operation.inputShape(model)
return if (input.memberNames.contains("accountId")) {
AccountIdAutofill()
- } else null
+ } else {
+ null
+ }
}
}
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt
index 7bfc3c4e424..5ba71e2f20c 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt
@@ -6,35 +6,21 @@
package software.amazon.smithy.rustsdk.customize.glacier
import software.amazon.smithy.model.shapes.OperationShape
-import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
-val Glacier: ShapeId = ShapeId.from("com.amazonaws.glacier#Glacier")
-
class GlacierDecorator : ClientCodegenDecorator {
override val name: String = "Glacier"
override val order: Byte = 0
- private fun applies(codegenContext: CodegenContext) = codegenContext.serviceShape.id == Glacier
-
override fun operationCustomizations(
codegenContext: ClientCodegenContext,
operation: OperationShape,
baseCustomizations: List,
- ): List {
- val extras = if (applies(codegenContext)) {
- val apiVersion = codegenContext.serviceShape.version
- listOfNotNull(
- ApiVersionHeader(apiVersion),
- TreeHashHeader.forOperation(operation, codegenContext.runtimeConfig),
- AccountIdAutofill.forOperation(operation, codegenContext.model),
- )
- } else {
- emptyList()
- }
- return baseCustomizations + extras
- }
+ ): List = baseCustomizations + listOfNotNull(
+ ApiVersionHeader(codegenContext.serviceShape.version),
+ TreeHashHeader.forOperation(operation, codegenContext.runtimeConfig),
+ AccountIdAutofill.forOperation(operation, codegenContext.model),
+ )
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt
index 023a663c429..c97357a2fd9 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt
@@ -33,13 +33,16 @@ private val UploadMultipartPart: ShapeId = ShapeId.from("com.amazonaws.glacier#U
private val Applies = setOf(UploadArchive, UploadMultipartPart)
class TreeHashHeader(private val runtimeConfig: RuntimeConfig) : OperationCustomization() {
- private val glacierChecksums = RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("glacier_checksums"))
+ private val glacierChecksums = RuntimeType.forInlineDependency(
+ InlineAwsDependency.forRustFile(
+ "glacier_checksums",
+ additionalDependency = TreeHashDependencies.toTypedArray(),
+ ),
+ )
+
override fun section(section: OperationSection): Writable {
return when (section) {
is OperationSection.MutateRequest -> writable {
- TreeHashDependencies.forEach { dep ->
- addDependency(dep)
- }
rustTemplate(
"""
#{glacier_checksums}::add_checksum_treehash(
@@ -49,6 +52,7 @@ class TreeHashHeader(private val runtimeConfig: RuntimeConfig) : OperationCustom
"glacier_checksums" to glacierChecksums, "BuildError" to runtimeConfig.operationBuildError(),
)
}
+
else -> emptySection
}
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt
index e1adcf46afd..007254c479d 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/route53/Route53Decorator.kt
@@ -26,26 +26,19 @@ import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rustsdk.InlineAwsDependency
import java.util.logging.Logger
-val Route53: ShapeId = ShapeId.from("com.amazonaws.route53#AWSDnsV20130401")
-
class Route53Decorator : ClientCodegenDecorator {
override val name: String = "Route53"
override val order: Byte = 0
private val logger: Logger = Logger.getLogger(javaClass.name)
private val resourceShapes = setOf(ShapeId.from("com.amazonaws.route53#ResourceId"), ShapeId.from("com.amazonaws.route53#ChangeId"))
- private fun applies(service: ServiceShape) = service.id == Route53
-
- override fun transformModel(service: ServiceShape, model: Model): Model {
- return model.letIf(applies(service)) {
- ModelTransformer.create().mapShapes(model) { shape ->
- shape.letIf(isResourceId(shape)) {
- logger.info("Adding TrimResourceId trait to $shape")
- (shape as MemberShape).toBuilder().addTrait(TrimResourceId()).build()
- }
+ override fun transformModel(service: ServiceShape, model: Model): Model =
+ ModelTransformer.create().mapShapes(model) { shape ->
+ shape.letIf(isResourceId(shape)) {
+ logger.info("Adding TrimResourceId trait to $shape")
+ (shape as MemberShape).toBuilder().addTrait(TrimResourceId()).build()
}
}
- }
override fun operationCustomizations(
codegenContext: ClientCodegenContext,
@@ -56,7 +49,9 @@ class Route53Decorator : ClientCodegenDecorator {
operation.inputShape(codegenContext.model).members().find { it.hasTrait() }
return if (hostedZoneMember != null) {
baseCustomizations + TrimResourceIdCustomization(codegenContext.symbolProvider.toMemberName(hostedZoneMember))
- } else baseCustomizations
+ } else {
+ baseCustomizations
+ }
}
private fun isResourceId(shape: Shape): Boolean {
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt
index d6c0f8257f4..bd0c5210080 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3Decorator.kt
@@ -22,19 +22,15 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.Cli
import software.amazon.smithy.rust.codegen.client.smithy.protocols.ClientRestXmlFactory
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
-import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestXml
import software.amazon.smithy.rust.codegen.core.smithy.traits.AllowInvalidXmlRoot
import software.amazon.smithy.rust.codegen.core.util.letIf
-import software.amazon.smithy.rustsdk.AwsRuntimeType
import software.amazon.smithy.rustsdk.endpoints.stripEndpointTrait
import software.amazon.smithy.rustsdk.getBuiltIn
import software.amazon.smithy.rustsdk.toWritable
@@ -52,38 +48,22 @@ class S3Decorator : ClientCodegenDecorator {
ShapeId.from("com.amazonaws.s3#GetObjectAttributesOutput"),
)
- private fun applies(serviceId: ShapeId) =
- serviceId == ShapeId.from("com.amazonaws.s3#AmazonS3")
-
override fun protocols(
serviceId: ShapeId,
currentProtocols: ProtocolMap,
- ): ProtocolMap =
- currentProtocols.letIf(applies(serviceId)) {
- it + mapOf(
- RestXmlTrait.ID to ClientRestXmlFactory { protocolConfig ->
- S3(protocolConfig)
- },
- )
- }
-
- override fun transformModel(service: ServiceShape, model: Model): Model {
- return model.letIf(applies(service.id)) {
- ModelTransformer.create().mapShapes(model) { shape ->
- shape.letIf(isInInvalidXmlRootAllowList(shape)) {
- logger.info("Adding AllowInvalidXmlRoot trait to $it")
- (it as StructureShape).toBuilder().addTrait(AllowInvalidXmlRoot()).build()
- }
- }.let(StripBucketFromHttpPath()::transform).let(stripEndpointTrait("RequestRoute"))
- }
- }
+ ): ProtocolMap = currentProtocols + mapOf(
+ RestXmlTrait.ID to ClientRestXmlFactory { protocolConfig ->
+ S3ProtocolOverride(protocolConfig)
+ },
+ )
- override fun libRsCustomizations(
- codegenContext: ClientCodegenContext,
- baseCustomizations: List,
- ): List = baseCustomizations.letIf(applies(codegenContext.serviceShape.id)) {
- it + S3PubUse()
- }
+ override fun transformModel(service: ServiceShape, model: Model): Model =
+ ModelTransformer.create().mapShapes(model) { shape ->
+ shape.letIf(isInInvalidXmlRootAllowList(shape)) {
+ logger.info("Adding AllowInvalidXmlRoot trait to $it")
+ (it as StructureShape).toBuilder().addTrait(AllowInvalidXmlRoot()).build()
+ }
+ }.let(StripBucketFromHttpPath()::transform).let(stripEndpointTrait("RequestRoute"))
override fun endpointCustomizations(codegenContext: ClientCodegenContext): List {
return listOf(object : EndpointCustomization {
@@ -108,35 +88,36 @@ class S3Decorator : ClientCodegenDecorator {
}
}
-class S3(codegenContext: CodegenContext) : RestXml(codegenContext) {
+class S3ProtocolOverride(codegenContext: CodegenContext) : RestXml(codegenContext) {
private val runtimeConfig = codegenContext.runtimeConfig
private val errorScope = arrayOf(
"Bytes" to RuntimeType.Bytes,
- "Error" to RuntimeType.genericError(runtimeConfig),
+ "ErrorMetadata" to RuntimeType.errorMetadata(runtimeConfig),
+ "ErrorBuilder" to RuntimeType.errorMetadataBuilder(runtimeConfig),
"HeaderMap" to RuntimeType.HttpHeaderMap,
"Response" to RuntimeType.HttpResponse,
"XmlDecodeError" to RuntimeType.smithyXml(runtimeConfig).resolve("decode::XmlDecodeError"),
"base_errors" to restXmlErrors,
- "s3_errors" to AwsRuntimeType.S3Errors,
)
- override fun parseHttpGenericError(operationShape: OperationShape): RuntimeType {
- return RuntimeType.forInlineFun("parse_http_generic_error", RustModule.private("xml_deser")) {
+ override fun parseHttpErrorMetadata(operationShape: OperationShape): RuntimeType {
+ return RuntimeType.forInlineFun("parse_http_error_metadata", RustModule.private("xml_deser")) {
rustBlockTemplate(
- "pub fn parse_http_generic_error(response: {Response}<#{Bytes}>) -> Result<#{Error}, #{XmlDecodeError}>",
+ "pub fn parse_http_error_metadata(response: {Response}<#{Bytes}>) -> Result<#{ErrorBuilder}, #{XmlDecodeError}>",
*errorScope,
) {
rustTemplate(
"""
+ // S3 HEAD responses have no response body to for an error code. Therefore,
+ // check the HTTP response status and populate an error code for 404s.
if response.body().is_empty() {
- let mut err = #{Error}::builder();
+ let mut builder = #{ErrorMetadata}::builder();
if response.status().as_u16() == 404 {
- err.code("NotFound");
+ builder = builder.code("NotFound");
}
- Ok(err.build())
+ Ok(builder)
} else {
- let base_err = #{base_errors}::parse_generic_error(response.body().as_ref())?;
- Ok(#{s3_errors}::parse_extended_error(base_err, response.headers()))
+ #{base_errors}::parse_error_metadata(response.body().as_ref())
}
""",
*errorScope,
@@ -145,16 +126,3 @@ class S3(codegenContext: CodegenContext) : RestXml(codegenContext) {
}
}
}
-
-class S3PubUse : LibRsCustomization() {
- override fun section(section: LibRsSection): Writable = when (section) {
- is LibRsSection.Body -> writable {
- rust(
- "pub use #T::ErrorExt;",
- AwsRuntimeType.S3Errors,
- )
- }
-
- else -> emptySection
- }
-}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExtendedRequestIdDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExtendedRequestIdDecorator.kt
new file mode 100644
index 00000000000..6b117b60da2
--- /dev/null
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExtendedRequestIdDecorator.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rustsdk.customize.s3
+
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rustsdk.BaseRequestIdDecorator
+import software.amazon.smithy.rustsdk.InlineAwsDependency
+
+class S3ExtendedRequestIdDecorator : BaseRequestIdDecorator() {
+ override val name: String = "S3ExtendedRequestIdDecorator"
+ override val order: Byte = 0
+
+ override val fieldName: String = "extended_request_id"
+ override val accessorFunctionName: String = "extended_request_id"
+
+ private val requestIdModule: RuntimeType =
+ RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("s3_request_id"))
+
+ override fun accessorTrait(codegenContext: ClientCodegenContext): RuntimeType =
+ requestIdModule.resolve("RequestIdExt")
+
+ override fun applyToError(codegenContext: ClientCodegenContext): RuntimeType =
+ requestIdModule.resolve("apply_extended_request_id")
+}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt
index 39e85d78988..3534258b189 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3control/S3ControlDecorator.kt
@@ -6,21 +6,41 @@
package software.amazon.smithy.rustsdk.customize.s3control
import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.node.Node
import software.amazon.smithy.model.shapes.ServiceShape
-import software.amazon.smithy.model.shapes.ShapeId
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rustsdk.endpoints.stripEndpointTrait
+import software.amazon.smithy.rustsdk.getBuiltIn
+import software.amazon.smithy.rustsdk.toWritable
class S3ControlDecorator : ClientCodegenDecorator {
override val name: String = "S3Control"
override val order: Byte = 0
- private fun applies(service: ServiceShape) =
- service.id == ShapeId.from("com.amazonaws.s3control#AWSS3ControlServiceV20180820")
- override fun transformModel(service: ServiceShape, model: Model): Model {
- if (!applies(service)) {
- return model
- }
- return stripEndpointTrait("AccountId")(model)
+ override fun transformModel(service: ServiceShape, model: Model): Model =
+ stripEndpointTrait("AccountId")(model)
+
+ override fun endpointCustomizations(codegenContext: ClientCodegenContext): List {
+ return listOf(object : EndpointCustomization {
+ override fun setBuiltInOnServiceConfig(name: String, value: Node, configBuilderRef: String): Writable? {
+ if (!name.startsWith("AWS::S3Control")) {
+ return null
+ }
+ val builtIn = codegenContext.getBuiltIn(name) ?: return null
+ return writable {
+ rustTemplate(
+ "let $configBuilderRef = $configBuilderRef.${builtIn.name.rustName()}(#{value});",
+ "value" to value.toWritable(),
+ )
+ }
+ }
+ },
+ )
}
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt
index 75d0555c8f7..a332dd30350 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/sts/STSDecorator.kt
@@ -7,7 +7,6 @@ package software.amazon.smithy.rustsdk.customize.sts
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.Shape
-import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.model.traits.RetryableTrait
@@ -22,24 +21,18 @@ class STSDecorator : ClientCodegenDecorator {
override val order: Byte = 0
private val logger: Logger = Logger.getLogger(javaClass.name)
- private fun applies(serviceId: ShapeId) =
- serviceId == ShapeId.from("com.amazonaws.sts#AWSSecurityTokenServiceV20110615")
-
private fun isIdpCommunicationError(shape: Shape): Boolean =
shape is StructureShape && shape.hasTrait() &&
shape.id.namespace == "com.amazonaws.sts" && shape.id.name == "IDPCommunicationErrorException"
- override fun transformModel(service: ServiceShape, model: Model): Model {
- return model.letIf(applies(service.id)) {
- ModelTransformer.create().mapShapes(model) { shape ->
- shape.letIf(isIdpCommunicationError(shape)) {
- logger.info("Adding @retryable trait to $shape and setting its error type to 'server'")
- (shape as StructureShape).toBuilder()
- .removeTrait(ErrorTrait.ID)
- .addTrait(ErrorTrait("server"))
- .addTrait(RetryableTrait.builder().build()).build()
- }
+ override fun transformModel(service: ServiceShape, model: Model): Model =
+ ModelTransformer.create().mapShapes(model) { shape ->
+ shape.letIf(isIdpCommunicationError(shape)) {
+ logger.info("Adding @retryable trait to $shape and setting its error type to 'server'")
+ (shape as StructureShape).toBuilder()
+ .removeTrait(ErrorTrait.ID)
+ .addTrait(ErrorTrait("server"))
+ .addTrait(RetryableTrait.builder().build()).build()
}
}
- }
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt
index ab9ce5bf1f7..7885db72d60 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/AwsEndpointDecorator.kt
@@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointTypesGenerator
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointsModule
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedConfigModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
@@ -27,12 +28,9 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customize.AdHocCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.adhocCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.core.util.extendIf
import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.thenSingletonListOf
@@ -91,14 +89,14 @@ class AwsEndpointDecorator : ClientCodegenDecorator {
}
}
- override fun libRsCustomizations(
- codegenContext: ClientCodegenContext,
- baseCustomizations: List,
- ): List {
- return baseCustomizations + PubUseEndpoint(codegenContext.runtimeConfig)
- }
-
override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ rustCrate.withModule(codegenContext.featureGatedConfigModule()) {
+ rust(
+ "pub use #T::endpoint::Endpoint;",
+ CargoDependency.smithyHttp(codegenContext.runtimeConfig).toType(),
+ )
+ }
+
val epTypes = EndpointTypesGenerator.fromContext(codegenContext)
if (epTypes.defaultResolver() == null) {
throw CodegenException(
@@ -252,23 +250,7 @@ class AwsEndpointDecorator : ClientCodegenDecorator {
}
ServiceConfig.ConfigStruct -> rust("endpoint_url: Option,")
- ServiceConfig.ConfigStructAdditionalDocs -> emptySection
- ServiceConfig.Extras -> emptySection
- }
- }
- }
-
- class PubUseEndpoint(private val runtimeConfig: RuntimeConfig) : LibRsCustomization() {
- override fun section(section: LibRsSection): Writable {
- return when (section) {
- is LibRsSection.Body -> writable {
- rust(
- "pub use #T::endpoint::Endpoint;",
- CargoDependency.smithyHttp(runtimeConfig).toType(),
- )
- }
-
- else -> emptySection
+ else -> {}
}
}
}
diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt
index 37ecbad73cc..9d620e11cb6 100644
--- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt
+++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt
@@ -17,7 +17,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointTypesG
import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.AttributeKind
-import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.escape
import software.amazon.smithy.rust.codegen.core.rustlang.join
import software.amazon.smithy.rust.codegen.core.rustlang.rust
@@ -25,6 +24,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.PublicImportSymbolProvider
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
@@ -146,8 +146,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test:
let _result = dbg!(#{invoke_operation});
#{assertion}
""",
- "capture_request" to CargoDependency.smithyClient(runtimeConfig)
- .withFeature("test-util").toType().resolve("test_connection::capture_request"),
+ "capture_request" to RuntimeType.captureRequest(runtimeConfig),
"conf" to config(testOperationInput),
"invoke_operation" to operationInvocation(testOperationInput),
"assertion" to writable {
diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt
index 1df1a0c0e04..ea2bfe026fa 100644
--- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt
+++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt
@@ -6,8 +6,8 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
-import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
import software.amazon.smithy.rust.codegen.core.testutil.tokioTest
@@ -96,8 +96,7 @@ class EndpointsCredentialsTest {
let auth_header = req.headers().get("AUTHORIZATION").unwrap().to_str().unwrap();
assert!(auth_header.contains("/us-west-2/foobaz/aws4_request"), "{}", auth_header);
""",
- "capture_request" to CargoDependency.smithyClient(context.runtimeConfig)
- .withFeature("test-util").toType().resolve("test_connection::capture_request"),
+ "capture_request" to RuntimeType.captureRequest(context.runtimeConfig),
"Credentials" to AwsCargoDependency.awsCredentialTypes(context.runtimeConfig)
.withFeature("test-util").toType().resolve("Credentials"),
"Region" to AwsRuntimeType.awsTypes(context.runtimeConfig).resolve("region::Region"),
@@ -120,8 +119,7 @@ class EndpointsCredentialsTest {
let auth_header = req.headers().get("AUTHORIZATION").unwrap().to_str().unwrap();
assert!(auth_header.contains("/region-custom-auth/name-custom-auth/aws4_request"), "{}", auth_header);
""",
- "capture_request" to CargoDependency.smithyClient(context.runtimeConfig)
- .withFeature("test-util").toType().resolve("test_connection::capture_request"),
+ "capture_request" to RuntimeType.captureRequest(context.runtimeConfig),
"Credentials" to AwsCargoDependency.awsCredentialTypes(context.runtimeConfig)
.withFeature("test-util").toType().resolve("Credentials"),
"Region" to AwsRuntimeType.awsTypes(context.runtimeConfig).resolve("region::Region"),
diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/HttpConnectorConfigCustomizationTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/HttpConnectorConfigCustomizationTest.kt
index 1307a46fbb8..b97952e0021 100644
--- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/HttpConnectorConfigCustomizationTest.kt
+++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/HttpConnectorConfigCustomizationTest.kt
@@ -7,30 +7,13 @@ package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
-import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
-import software.amazon.smithy.rust.codegen.core.testutil.rustSettings
class HttpConnectorConfigCustomizationTest {
@Test
fun `generates a valid config`() {
val project = TestWorkspace.testProject()
- val projectSettings = project.rustSettings()
- val codegenContext = awsTestCodegenContext(
- coreRustSettings = CoreRustSettings(
- service = projectSettings.service,
- moduleName = projectSettings.moduleName,
- moduleVersion = projectSettings.moduleVersion,
- moduleAuthors = projectSettings.moduleAuthors,
- moduleDescription = projectSettings.moduleDescription,
- moduleRepository = projectSettings.moduleRepository,
- runtimeConfig = AwsTestRuntimeConfig,
- codegenConfig = projectSettings.codegenConfig,
- license = projectSettings.license,
- examplesUri = projectSettings.examplesUri,
- customizationConfig = projectSettings.customizationConfig,
- ),
- )
+ val codegenContext = awsTestCodegenContext()
validateConfigCustomizations(HttpConnectorConfigCustomization(codegenContext), project)
}
}
diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/RegionProviderConfigTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/RegionProviderConfigTest.kt
index 8d69fb2c868..9d2e865a646 100644
--- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/RegionProviderConfigTest.kt
+++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/RegionProviderConfigTest.kt
@@ -6,8 +6,8 @@
package software.amazon.smithy.rustsdk
import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.client.testutil.testClientRustSettings
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
-import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.rustSettings
@@ -15,21 +15,12 @@ internal class RegionProviderConfigTest {
@Test
fun `generates a valid config`() {
val project = TestWorkspace.testProject()
- val projectSettings = project.rustSettings()
- val coreRustSettings = CoreRustSettings(
- service = projectSettings.service,
- moduleName = projectSettings.moduleName,
- moduleVersion = projectSettings.moduleVersion,
- moduleAuthors = projectSettings.moduleAuthors,
- moduleDescription = projectSettings.moduleDescription,
- moduleRepository = projectSettings.moduleRepository,
- runtimeConfig = AwsTestRuntimeConfig,
- codegenConfig = projectSettings.codegenConfig,
- license = projectSettings.license,
- examplesUri = projectSettings.examplesUri,
- customizationConfig = projectSettings.customizationConfig,
+ val codegenContext = awsTestCodegenContext(
+ settings = testClientRustSettings(
+ moduleName = project.rustSettings().moduleName,
+ runtimeConfig = AwsTestRuntimeConfig,
+ ),
)
- val codegenContext = awsTestCodegenContext(coreRustSettings = coreRustSettings)
validateConfigCustomizations(RegionProviderConfig(codegenContext), project)
}
}
diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt
index 8db0d6a1dff..d0a619b4679 100644
--- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt
+++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/TestUtil.kt
@@ -8,14 +8,15 @@ package software.amazon.smithy.rustsdk
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.node.ObjectNode
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustSettings
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
-import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.testutil.testClientRustSettings
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeCrateLocation
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
-import software.amazon.smithy.rust.codegen.core.testutil.testRustSettings
import java.io.File
// In aws-sdk-codegen, the working dir when gradle runs tests is actually `./aws`. So, to find the smithy runtime, we need
@@ -28,10 +29,10 @@ val AwsTestRuntimeConfig = TestRuntimeConfig.copy(
},
)
-fun awsTestCodegenContext(model: Model? = null, coreRustSettings: CoreRustSettings?) =
- testCodegenContext(
+fun awsTestCodegenContext(model: Model? = null, settings: ClientRustSettings? = null) =
+ testClientCodegenContext(
model ?: "namespace test".asSmithyModel(),
- settings = coreRustSettings ?: testRustSettings(runtimeConfig = AwsTestRuntimeConfig),
+ settings = settings ?: testClientRustSettings(runtimeConfig = AwsTestRuntimeConfig),
)
fun awsSdkIntegrationTest(
@@ -39,9 +40,10 @@ fun awsSdkIntegrationTest(
test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
) =
clientIntegrationTest(
- model, runtimeConfig = AwsTestRuntimeConfig,
- additionalSettings = ObjectNode.builder()
- .withMember(
+ model,
+ IntegrationTestParams(
+ runtimeConfig = AwsTestRuntimeConfig,
+ additionalSettings = ObjectNode.builder().withMember(
"customizationConfig",
ObjectNode.builder()
.withMember(
@@ -51,6 +53,7 @@ fun awsSdkIntegrationTest(
.build(),
).build(),
)
- .withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(),
+ .withMember("codegen", ObjectNode.builder().withMember("includeFluentClient", false).build()).build(),
+ ),
test = test,
)
diff --git a/aws/sdk/build.gradle.kts b/aws/sdk/build.gradle.kts
index e2b3d0b72bf..31478777048 100644
--- a/aws/sdk/build.gradle.kts
+++ b/aws/sdk/build.gradle.kts
@@ -52,7 +52,9 @@ dependencies {
// Class and functions for service and protocol membership for SDK generation
-val awsServices: AwsServices by lazy { discoverServices(properties.get("aws.sdk.models.path"), loadServiceMembership()) }
+val awsServices: AwsServices by lazy {
+ discoverServices(properties.get("aws.sdk.models.path"), loadServiceMembership())
+}
val eventStreamAllowList: Set by lazy { eventStreamAllowList() }
val crateVersioner by lazy { aws.sdk.CrateVersioner.defaultFor(rootProject, properties) }
@@ -97,7 +99,8 @@ fun generateSmithyBuild(services: AwsServices): String {
"codegen": {
"includeFluentClient": false,
"renameErrors": false,
- "eventStreamAllowList": [$eventStreamAllowListMembers]
+ "eventStreamAllowList": [$eventStreamAllowListMembers],
+ "enableNewCrateOrganizationScheme": false
},
"service": "${service.service}",
"module": "$moduleName",
diff --git a/aws/sdk/integration-tests/Cargo.toml b/aws/sdk/integration-tests/Cargo.toml
index 406b718a94b..a36345cda0f 100644
--- a/aws/sdk/integration-tests/Cargo.toml
+++ b/aws/sdk/integration-tests/Cargo.toml
@@ -15,4 +15,5 @@ members = [
"s3control",
"sts",
"transcribestreaming",
+ "using-native-tls-instead-of-rustls",
]
diff --git a/aws/sdk/integration-tests/kms/tests/integration.rs b/aws/sdk/integration-tests/kms/tests/integration.rs
index baa39ef6a48..8c9bd1e1058 100644
--- a/aws/sdk/integration-tests/kms/tests/integration.rs
+++ b/aws/sdk/integration-tests/kms/tests/integration.rs
@@ -6,6 +6,7 @@
use aws_http::user_agent::AwsUserAgent;
use aws_sdk_kms as kms;
use aws_sdk_kms::middleware::DefaultMiddleware;
+use aws_sdk_kms::types::RequestId;
use aws_smithy_client::test_connection::TestConnection;
use aws_smithy_client::{Client as CoreClient, SdkError};
use aws_smithy_http::body::SdkBody;
diff --git a/aws/sdk/integration-tests/kms/tests/sensitive-it.rs b/aws/sdk/integration-tests/kms/tests/sensitive-it.rs
index 5a97651d83e..00f3c8d95e0 100644
--- a/aws/sdk/integration-tests/kms/tests/sensitive-it.rs
+++ b/aws/sdk/integration-tests/kms/tests/sensitive-it.rs
@@ -19,12 +19,17 @@ use kms::types::Blob;
#[test]
fn validate_sensitive_trait() {
+ let builder = GenerateRandomOutput::builder().plaintext(Blob::new("some output"));
+ assert_eq!(
+ format!("{:?}", builder),
+ "Builder { plaintext: \"*** Sensitive Data Redacted ***\", _request_id: None }"
+ );
let output = GenerateRandomOutput::builder()
.plaintext(Blob::new("some output"))
.build();
assert_eq!(
format!("{:?}", output),
- "GenerateRandomOutput { plaintext: \"*** Sensitive Data Redacted ***\" }"
+ "GenerateRandomOutput { plaintext: \"*** Sensitive Data Redacted ***\", _request_id: None }"
);
}
diff --git a/aws/sdk/integration-tests/lambda/tests/request_id.rs b/aws/sdk/integration-tests/lambda/tests/request_id.rs
new file mode 100644
index 00000000000..ab3ede5f0ac
--- /dev/null
+++ b/aws/sdk/integration-tests/lambda/tests/request_id.rs
@@ -0,0 +1,39 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+use aws_sdk_lambda::error::ListFunctionsError;
+use aws_sdk_lambda::operation::ListFunctions;
+use aws_sdk_lambda::types::RequestId;
+use aws_smithy_http::response::ParseHttpResponse;
+use bytes::Bytes;
+
+#[test]
+fn get_request_id_from_unmodeled_error() {
+ let resp = http::Response::builder()
+ .header("x-amzn-RequestId", "correct-request-id")
+ .header("X-Amzn-Errortype", "ListFunctions")
+ .status(500)
+ .body("{}")
+ .unwrap();
+ let err = ListFunctions::new()
+ .parse_loaded(&resp.map(Bytes::from))
+ .expect_err("status was 500, this is an error");
+ assert!(matches!(err, ListFunctionsError::Unhandled(_)));
+ assert_eq!(Some("correct-request-id"), err.request_id());
+ assert_eq!(Some("correct-request-id"), err.meta().request_id());
+}
+
+#[test]
+fn get_request_id_from_successful_response() {
+ let resp = http::Response::builder()
+ .header("x-amzn-RequestId", "correct-request-id")
+ .status(200)
+ .body(r#"{"Functions":[],"NextMarker":null}"#)
+ .unwrap();
+ let output = ListFunctions::new()
+ .parse_loaded(&resp.map(Bytes::from))
+ .expect("valid successful response");
+ assert_eq!(Some("correct-request-id"), output.request_id());
+}
diff --git a/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs b/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs
deleted file mode 100644
index 46b1fc50f72..00000000000
--- a/aws/sdk/integration-tests/s3/tests/custom-error-deserializer.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-use aws_sdk_s3::operation::GetObject;
-use aws_sdk_s3::ErrorExt;
-use aws_smithy_http::response::ParseHttpResponse;
-use bytes::Bytes;
-
-#[test]
-fn deserialize_extended_errors() {
- let resp = http::Response::builder()
- .header(
- "x-amz-id-2",
- "gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP",
- )
- .header("x-amz-request-id", "3B3C7C725673C630")
- .status(404)
- .body(
- r#"
-
- NoSuchKey
- The resource you requested does not exist
- /mybucket/myfoto.jpg
- 4442587FB7D0A2F9
-"#,
- )
- .unwrap();
- let err = GetObject::new()
- .parse_loaded(&resp.map(Bytes::from))
- .expect_err("status was 404, this is an error");
- assert_eq!(
- err.meta().extended_request_id(),
- Some("gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP")
- );
- assert_eq!(err.meta().request_id(), Some("4442587FB7D0A2F9"));
-}
diff --git a/aws/sdk/integration-tests/s3/tests/endpoints.rs b/aws/sdk/integration-tests/s3/tests/endpoints.rs
index 93c37f5c872..02b1718569e 100644
--- a/aws/sdk/integration-tests/s3/tests/endpoints.rs
+++ b/aws/sdk/integration-tests/s3/tests/endpoints.rs
@@ -16,7 +16,7 @@ fn test_client(update_builder: fn(Builder) -> Builder) -> (CaptureRequestReceive
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.region(Region::new("us-west-4"))
- .http_connector(conn.clone())
+ .http_connector(conn)
.build();
let client = Client::from_conf(update_builder(Builder::from(&sdk_config)).build());
(captured_request, client)
diff --git a/aws/sdk/integration-tests/s3/tests/presigning.rs b/aws/sdk/integration-tests/s3/tests/presigning.rs
index 341c7f3e0ce..c68424a5946 100644
--- a/aws/sdk/integration-tests/s3/tests/presigning.rs
+++ b/aws/sdk/integration-tests/s3/tests/presigning.rs
@@ -130,3 +130,15 @@ async fn test_presigned_upload_part() -> Result<(), Box> {
);
Ok(())
}
+
+#[tokio::test]
+async fn test_presigning_object_lambda() -> Result<(), Box> {
+ let presigned = presign_input!(s3::input::GetObjectInput::builder()
+ .bucket("arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:my-banner-ap-name")
+ .key("test2.txt")
+ .build()
+ .unwrap());
+ // since the URI is `my-banner-api-name...` we know EP2 is working properly for presigning
+ assert_eq!(presigned.uri().to_string(), "https://my-banner-ap-name-123456789012.s3-object-lambda.us-west-2.amazonaws.com/test2.txt?x-id=GetObject&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ANOTREAL%2F20090213%2Fus-west-2%2Fs3-object-lambda%2Faws4_request&X-Amz-Date=20090213T233131Z&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=027976453050b6f9cca7af80a59c05ee572b462e0fc1ef564c59412b903fcdf2&X-Amz-Security-Token=notarealsessiontoken");
+ Ok(())
+}
diff --git a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs
index 858d0138410..7714becfe55 100644
--- a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs
+++ b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs
@@ -71,7 +71,7 @@ async fn test_s3_signer_query_string_with_all_valid_chars() {
#[tokio::test]
#[ignore]
async fn test_query_strings_are_correctly_encoded() {
- use aws_sdk_s3::error::{ListObjectsV2Error, ListObjectsV2ErrorKind};
+ use aws_sdk_s3::error::ListObjectsV2Error;
use aws_smithy_http::result::SdkError;
tracing_subscriber::fmt::init();
@@ -92,22 +92,19 @@ async fn test_query_strings_are_correctly_encoded() {
.send()
.await;
if let Err(SdkError::ServiceError(context)) = res {
- let ListObjectsV2Error { kind, .. } = context.err();
- match kind {
- ListObjectsV2ErrorKind::Unhandled(e)
+ match context.err() {
+ ListObjectsV2Error::Unhandled(e)
if e.to_string().contains("SignatureDoesNotMatch") =>
{
chars_that_break_signing.push(byte);
}
- ListObjectsV2ErrorKind::Unhandled(e) if e.to_string().contains("InvalidUri") => {
+ ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidUri") => {
chars_that_break_uri_parsing.push(byte);
}
- ListObjectsV2ErrorKind::Unhandled(e)
- if e.to_string().contains("InvalidArgument") =>
- {
+ ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidArgument") => {
chars_that_are_invalid_arguments.push(byte);
}
- ListObjectsV2ErrorKind::Unhandled(e) if e.to_string().contains("InvalidToken") => {
+ ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidToken") => {
panic!("refresh your credentials and run this test again");
}
e => todo!("unexpected error: {:?}", e),
diff --git a/aws/sdk/integration-tests/s3/tests/request_id.rs b/aws/sdk/integration-tests/s3/tests/request_id.rs
new file mode 100644
index 00000000000..957dd8cb284
--- /dev/null
+++ b/aws/sdk/integration-tests/s3/tests/request_id.rs
@@ -0,0 +1,148 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+use aws_sdk_s3::error::GetObjectError;
+use aws_sdk_s3::operation::{GetObject, ListBuckets};
+use aws_sdk_s3::types::{RequestId, RequestIdExt};
+use aws_smithy_http::body::SdkBody;
+use aws_smithy_http::operation;
+use aws_smithy_http::response::ParseHttpResponse;
+use bytes::Bytes;
+
+#[test]
+fn get_request_id_from_modeled_error() {
+ let resp = http::Response::builder()
+ .header("x-amz-request-id", "correct-request-id")
+ .header("x-amz-id-2", "correct-extended-request-id")
+ .status(404)
+ .body(
+ r#"
+
+ NoSuchKey
+ The resource you requested does not exist
+ /mybucket/myfoto.jpg
+ incorrect-request-id
+ "#,
+ )
+ .unwrap();
+ let err = GetObject::new()
+ .parse_loaded(&resp.map(Bytes::from))
+ .expect_err("status was 404, this is an error");
+ assert!(matches!(err, GetObjectError::NoSuchKey(_)));
+ assert_eq!(Some("correct-request-id"), err.request_id());
+ assert_eq!(Some("correct-request-id"), err.meta().request_id());
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ err.extended_request_id()
+ );
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ err.meta().extended_request_id()
+ );
+}
+
+#[test]
+fn get_request_id_from_unmodeled_error() {
+ let resp = http::Response::builder()
+ .header("x-amz-request-id", "correct-request-id")
+ .header("x-amz-id-2", "correct-extended-request-id")
+ .status(500)
+ .body(
+ r#"
+
+ SomeUnmodeledError
+ Something bad happened
+ /mybucket/myfoto.jpg
+ incorrect-request-id
+ "#,
+ )
+ .unwrap();
+ let err = GetObject::new()
+ .parse_loaded(&resp.map(Bytes::from))
+ .expect_err("status 500");
+ assert!(matches!(err, GetObjectError::Unhandled(_)));
+ assert_eq!(Some("correct-request-id"), err.request_id());
+ assert_eq!(Some("correct-request-id"), err.meta().request_id());
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ err.extended_request_id()
+ );
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ err.meta().extended_request_id()
+ );
+}
+
+#[test]
+fn get_request_id_from_successful_nonstreaming_response() {
+ let resp = http::Response::builder()
+ .header("x-amz-request-id", "correct-request-id")
+ .header("x-amz-id-2", "correct-extended-request-id")
+ .status(200)
+ .body(
+ r#"
+
+ some-idsome-display-name
+
+ "#,
+ )
+ .unwrap();
+ let output = ListBuckets::new()
+ .parse_loaded(&resp.map(Bytes::from))
+ .expect("valid successful response");
+ assert_eq!(Some("correct-request-id"), output.request_id());
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ output.extended_request_id()
+ );
+}
+
+#[test]
+fn get_request_id_from_successful_streaming_response() {
+ let resp = http::Response::builder()
+ .header("x-amz-request-id", "correct-request-id")
+ .header("x-amz-id-2", "correct-extended-request-id")
+ .status(200)
+ .body(SdkBody::from("some streaming file data"))
+ .unwrap();
+ let mut resp = operation::Response::new(resp);
+ let output = GetObject::new()
+ .parse_unloaded(&mut resp)
+ .expect("valid successful response");
+ assert_eq!(Some("correct-request-id"), output.request_id());
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ output.extended_request_id()
+ );
+}
+
+// Verify that the conversion from operation error to the top-level service error maintains the request ID
+#[test]
+fn conversion_to_service_error_maintains_request_id() {
+ let resp = http::Response::builder()
+ .header("x-amz-request-id", "correct-request-id")
+ .header("x-amz-id-2", "correct-extended-request-id")
+ .status(404)
+ .body(
+ r#"
+
+ NoSuchKey
+ The resource you requested does not exist
+ /mybucket/myfoto.jpg
+ incorrect-request-id
+ "#,
+ )
+ .unwrap();
+ let err = GetObject::new()
+ .parse_loaded(&resp.map(Bytes::from))
+ .expect_err("status was 404, this is an error");
+
+ let service_error: aws_sdk_s3::Error = err.into();
+ assert_eq!(Some("correct-request-id"), service_error.request_id());
+ assert_eq!(
+ Some("correct-extended-request-id"),
+ service_error.extended_request_id()
+ );
+}
diff --git a/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs b/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs
index 6fe9895cd3b..3b546bbb0b9 100644
--- a/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs
+++ b/aws/sdk/integration-tests/sts/tests/retry_idp_comms_err.rs
@@ -4,24 +4,21 @@
*/
use aws_sdk_sts as sts;
-use aws_smithy_types::error::Error as ErrorMeta;
+use aws_smithy_types::error::ErrorMetadata;
use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind};
-use sts::error::{
- AssumeRoleWithWebIdentityError, AssumeRoleWithWebIdentityErrorKind,
- IdpCommunicationErrorException,
-};
+use sts::error::{AssumeRoleWithWebIdentityError, IdpCommunicationErrorException};
#[tokio::test]
async fn idp_comms_err_retryable() {
- let error = AssumeRoleWithWebIdentityError::new(
- AssumeRoleWithWebIdentityErrorKind::IdpCommunicationErrorException(
- IdpCommunicationErrorException::builder()
- .message("test")
- .build(),
- ),
- ErrorMeta::builder()
- .code("IDPCommunicationError")
+ let error = AssumeRoleWithWebIdentityError::IdpCommunicationErrorException(
+ IdpCommunicationErrorException::builder()
.message("test")
+ .meta(
+ ErrorMetadata::builder()
+ .code("IDPCommunicationError")
+ .message("test")
+ .build(),
+ )
.build(),
);
assert_eq!(
diff --git a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs
index f48515038a9..fe6b028820f 100644
--- a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs
+++ b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs
@@ -4,9 +4,7 @@
*/
use async_stream::stream;
-use aws_sdk_transcribestreaming::error::{
- AudioStreamError, TranscriptResultStreamError, TranscriptResultStreamErrorKind,
-};
+use aws_sdk_transcribestreaming::error::{AudioStreamError, TranscriptResultStreamError};
use aws_sdk_transcribestreaming::model::{
AudioEvent, AudioStream, LanguageCode, MediaEncoding, TranscriptResultStream,
};
@@ -76,10 +74,7 @@ async fn test_error() {
match output.transcript_result_stream.recv().await {
Err(SdkError::ServiceError(context)) => match context.err() {
- TranscriptResultStreamError {
- kind: TranscriptResultStreamErrorKind::BadRequestException(err),
- ..
- } => {
+ TranscriptResultStreamError::BadRequestException(err) => {
assert_eq!(
Some("A complete signal was sent without the preceding empty frame."),
err.message()
diff --git a/aws/sdk/integration-tests/using-native-tls-instead-of-rustls/Cargo.toml b/aws/sdk/integration-tests/using-native-tls-instead-of-rustls/Cargo.toml
new file mode 100644
index 00000000000..3642d7ba248
--- /dev/null
+++ b/aws/sdk/integration-tests/using-native-tls-instead-of-rustls/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "using-native-tls-instead-of-rustls"
+version = "0.1.0"
+authors = ["AWS Rust SDK Team "]
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dev-dependencies]
+# aws-config pulls in rustls and several other things by default. We have to disable defaults in order to use native-tls
+# and then manually bring the other defaults back
+aws-config = { path = "../../build/aws-sdk/sdk/aws-config", default-features = false, features = [
+ "native-tls",
+ "rt-tokio",
+] }
+# aws-sdk-s3 brings in rustls by default so we disable that in order to use native-tls only
+aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", default-features = false, features = [
+ "native-tls",
+] }
+tokio = { version = "1.20.1", features = ["rt", "macros"] }
diff --git a/aws/sdk/integration-tests/using-native-tls-instead-of-rustls/tests/no-rustls-in-dependency.rs b/aws/sdk/integration-tests/using-native-tls-instead-of-rustls/tests/no-rustls-in-dependency.rs
new file mode 100644
index 00000000000..dddeebc4795
--- /dev/null
+++ b/aws/sdk/integration-tests/using-native-tls-instead-of-rustls/tests/no-rustls-in-dependency.rs
@@ -0,0 +1,52 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/// The SDK defaults to using RusTLS by default but you can also use [`native_tls`](https://github.com/sfackler/rust-native-tls)
+/// which will choose a TLS implementation appropriate for your platform. This test looks much like
+/// any other. Activating and deactivating `features` in your app's `Cargo.toml` is all that's needed.
+
+async fn list_buckets() -> Result<(), aws_sdk_s3::Error> {
+ let sdk_config = aws_config::load_from_env().await;
+ let client = aws_sdk_s3::Client::new(&sdk_config);
+
+ let _resp = client.list_buckets().send().await?;
+
+ Ok(())
+}
+
+/// You can run this test to ensure that it is only using `native-tls` and
+/// that nothing is pulling in `rustls` as a dependency
+#[test]
+#[should_panic = "error: package ID specification `rustls` did not match any packages"]
+fn test_rustls_is_not_in_dependency_tree() {
+ let cargo_location = std::env::var("CARGO").unwrap();
+ let cargo_command = std::process::Command::new(&cargo_location)
+ .arg("tree")
+ .arg("--invert")
+ .arg("rustls")
+ .output()
+ .expect("failed to run 'cargo tree'");
+
+ let stderr = String::from_utf8_lossy(&cargo_command.stderr);
+
+ // We expect the call to `cargo tree` to error out. If it did, we panic with the resulting
+ // message here. In the case that no error message is set, that's bad.
+ if !stderr.is_empty() {
+ panic!("{}", stderr);
+ }
+
+ // Uh oh. We expected an error message but got none, likely because `cargo tree` found
+ // `rustls` in our dependencies. We'll print out the message we got to see what went wrong.
+ let stdout = String::from_utf8_lossy(&cargo_command.stdout);
+
+ println!("{}", stdout)
+}
+
+// NOTE: not currently run in CI, separate PR will set up a with-creds CI runner
+#[tokio::test]
+#[ignore]
+async fn needs_creds_native_tls_works() {
+ list_buckets().await.expect("should succeed")
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 2c06af67257..a2d6385db4e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,9 +14,7 @@ buildscript {
}
}
-plugins {
- kotlin("jvm") version "1.3.72" apply false
-}
+plugins { }
allprojects {
repositories {
@@ -61,7 +59,7 @@ tasks.register("ktlint") {
group = "Verification"
classpath = configurations.getByName("ktlint")
mainClass.set("com.pinterest.ktlint.Main")
- args = listOf("--verbose", "--relative", "--") + lintPaths
+ args = listOf("--log-level=info", "--relative", "--") + lintPaths
// https://github.com/pinterest/ktlint/issues/1195#issuecomment-1009027802
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
@@ -71,7 +69,7 @@ tasks.register("ktlintFormat") {
group = "formatting"
classpath = configurations.getByName("ktlint")
mainClass.set("com.pinterest.ktlint.Main")
- args = listOf("--verbose", "--relative", "--format", "--") + lintPaths
+ args = listOf("--log-level=info", "--relative", "--format", "--") + lintPaths
// https://github.com/pinterest/ktlint/issues/1195#issuecomment-1009027802
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
diff --git a/buildSrc/src/main/kotlin/CodegenTestCommon.kt b/buildSrc/src/main/kotlin/CodegenTestCommon.kt
index d975d1a521e..033d418c843 100644
--- a/buildSrc/src/main/kotlin/CodegenTestCommon.kt
+++ b/buildSrc/src/main/kotlin/CodegenTestCommon.kt
@@ -62,7 +62,7 @@ enum class Cargo(val toString: String) {
CHECK("cargoCheck"),
TEST("cargoTest"),
DOCS("cargoDoc"),
- CLIPPY("cargoClippy");
+ CLIPPY("cargoClippy"),
}
private fun generateCargoWorkspace(pluginName: String, tests: List) =
@@ -86,7 +86,9 @@ private fun codegenTests(properties: PropertyRetriever, allTests: List {
AllCargoCommands
}
require(ret.isNotEmpty()) {
- "None of the provided cargo commands (`$cargoCommandsOverride`) are valid cargo commands (`${AllCargoCommands.map { it.toString }}`)"
+ "None of the provided cargo commands (`$cargoCommandsOverride`) are valid cargo commands (`${AllCargoCommands.map {
+ it.toString
+ }}`)"
}
return ret
}
diff --git a/buildSrc/src/main/kotlin/CrateSet.kt b/buildSrc/src/main/kotlin/CrateSet.kt
index c915649f617..90e1df95ab3 100644
--- a/buildSrc/src/main/kotlin/CrateSet.kt
+++ b/buildSrc/src/main/kotlin/CrateSet.kt
@@ -21,6 +21,7 @@ object CrateSet {
"aws-smithy-checksums",
"aws-smithy-eventstream",
"aws-smithy-http",
+ "aws-smithy-http-auth",
"aws-smithy-http-tower",
"aws-smithy-json",
"aws-smithy-protocol-test",
diff --git a/buildSrc/src/main/kotlin/aws/sdk/DocsLandingPage.kt b/buildSrc/src/main/kotlin/aws/sdk/DocsLandingPage.kt
index d522a02f4a3..715917c4db2 100644
--- a/buildSrc/src/main/kotlin/aws/sdk/DocsLandingPage.kt
+++ b/buildSrc/src/main/kotlin/aws/sdk/DocsLandingPage.kt
@@ -44,7 +44,9 @@ fun Project.docsLandingPage(awsServices: AwsServices, outputPath: File) {
/**
* Generate a link to the examples for a given service
*/
-private fun examplesLink(service: AwsService, project: Project) = service.examplesUri(project)?.let { "([examples]($it))" }
+private fun examplesLink(service: AwsService, project: Project) = service.examplesUri(project)?.let {
+ "([examples]($it))"
+}
/**
* Generate a link to the docs
diff --git a/buildSrc/src/main/kotlin/aws/sdk/ModelMetadata.kt b/buildSrc/src/main/kotlin/aws/sdk/ModelMetadata.kt
index a70aa7a0d68..95d65fd106d 100644
--- a/buildSrc/src/main/kotlin/aws/sdk/ModelMetadata.kt
+++ b/buildSrc/src/main/kotlin/aws/sdk/ModelMetadata.kt
@@ -11,7 +11,7 @@ import java.io.File
enum class ChangeType {
UNCHANGED,
FEATURE,
- DOCUMENTATION
+ DOCUMENTATION,
}
/** Model metadata toml file */
diff --git a/buildSrc/src/main/kotlin/aws/sdk/ServiceLoader.kt b/buildSrc/src/main/kotlin/aws/sdk/ServiceLoader.kt
index fdc6448b0c5..44510d1b118 100644
--- a/buildSrc/src/main/kotlin/aws/sdk/ServiceLoader.kt
+++ b/buildSrc/src/main/kotlin/aws/sdk/ServiceLoader.kt
@@ -135,9 +135,9 @@ fun Project.discoverServices(awsModelsPath: String?, serviceMembership: Membersh
serviceMembership.exclusions.forEach { disabledService ->
check(baseModules.contains(disabledService)) {
"Service $disabledService was explicitly disabled but no service was generated with that name. Generated:\n ${
- baseModules.joinToString(
- "\n ",
- )
+ baseModules.joinToString(
+ "\n ",
+ )
}"
}
}
@@ -206,7 +206,9 @@ fun parseMembership(rawList: String): Membership {
}
val conflictingMembers = inclusions.intersect(exclusions)
- require(conflictingMembers.isEmpty()) { "$conflictingMembers specified both for inclusion and exclusion in $rawList" }
+ require(conflictingMembers.isEmpty()) {
+ "$conflictingMembers specified both for inclusion and exclusion in $rawList"
+ }
return Membership(inclusions, exclusions)
}
diff --git a/codegen-client-test/build.gradle.kts b/codegen-client-test/build.gradle.kts
index 434bcef65f0..f3796a6911f 100644
--- a/codegen-client-test/build.gradle.kts
+++ b/codegen-client-test/build.gradle.kts
@@ -79,6 +79,11 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels ->
""".trimIndent(),
imports = listOf("$commonModels/naming-obstacle-course-ops.smithy"),
),
+ CodegenTest(
+ "casing#ACRONYMInside_Service",
+ "naming_test_casing",
+ imports = listOf("$commonModels/naming-obstacle-course-casing.smithy"),
+ ),
CodegenTest(
"naming_obs_structs#NamingObstacleCourseStructs",
"naming_test_structs",
diff --git a/codegen-client/build.gradle.kts b/codegen-client/build.gradle.kts
index ba6ac6ac1b4..62d543beb4d 100644
--- a/codegen-client/build.gradle.kts
+++ b/codegen-client/build.gradle.kts
@@ -28,6 +28,10 @@ dependencies {
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
implementation("software.amazon.smithy:smithy-waiters:$smithyVersion")
implementation("software.amazon.smithy:smithy-rules-engine:$smithyVersion")
+
+ // `smithy.framework#ValidationException` is defined here, which is used in event stream
+// marshalling/unmarshalling tests.
+ testImplementation("software.amazon.smithy:smithy-validation-model:$smithyVersion")
}
tasks.compileKotlin {
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt
index 44099cceb1b..881cecdad86 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt
@@ -6,6 +6,7 @@
package software.amazon.smithy.rust.codegen.client.smithy
import software.amazon.smithy.build.PluginContext
+import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.NullableIndex
import software.amazon.smithy.model.shapes.OperationShape
@@ -16,38 +17,42 @@ import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.shapes.UnionShape
import software.amazon.smithy.model.traits.EnumTrait
+import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.model.transform.ModelTransformer
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.ClientEnumGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceGenerator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorGenerator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.OperationErrorGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
import software.amazon.smithy.rust.codegen.client.smithy.protocols.ClientProtocolLoader
import software.amazon.smithy.rust.codegen.client.smithy.transformers.AddErrorMessage
import software.amazon.smithy.rust.codegen.client.smithy.transformers.RemoveEventStreamOperations
+import software.amazon.smithy.rust.codegen.core.rustlang.EscapeFor
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
+import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
import software.amazon.smithy.rust.codegen.core.smithy.DirectedWalker
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
-import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig
+import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig
+import software.amazon.smithy.rust.codegen.core.smithy.contextName
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.eventStreamErrorSymbol
-import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock
+import software.amazon.smithy.rust.codegen.core.smithy.module
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory
-import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait
import software.amazon.smithy.rust.codegen.core.smithy.transformers.EventStreamNormalizer
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer
-import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamErrors
-import software.amazon.smithy.rust.codegen.core.smithy.transformers.operationErrors
import software.amazon.smithy.rust.codegen.core.util.CommandFailed
import software.amazon.smithy.rust.codegen.core.util.getTrait
import software.amazon.smithy.rust.codegen.core.util.hasTrait
import software.amazon.smithy.rust.codegen.core.util.isEventStream
import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.runCommand
+import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import java.util.logging.Logger
/**
@@ -57,7 +62,6 @@ class ClientCodegenVisitor(
context: PluginContext,
private val codegenDecorator: ClientCodegenDecorator,
) : ShapeVisitor.Default() {
-
private val logger = Logger.getLogger(javaClass.name)
private val settings = ClientRustSettings.from(context.model, context.settings)
@@ -70,12 +74,21 @@ class ClientCodegenVisitor(
private val protocolGenerator: ClientProtocolGenerator
init {
- val symbolVisitorConfig =
- SymbolVisitorConfig(
- runtimeConfig = settings.runtimeConfig,
- renameExceptions = settings.codegenConfig.renameExceptions,
- nullabilityCheckMode = NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1,
- )
+ val rustSymbolProviderConfig = RustSymbolProviderConfig(
+ runtimeConfig = settings.runtimeConfig,
+ renameExceptions = settings.codegenConfig.renameExceptions,
+ nullabilityCheckMode = NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1,
+ moduleProvider = when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> ClientModuleProvider
+ else -> OldModuleSchemeClientModuleProvider
+ },
+ nameBuilderFor = { symbol ->
+ when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> "${symbol.name}Builder"
+ else -> "Builder"
+ }
+ },
+ )
val baseModel = baselineTransform(context.model)
val untransformedService = settings.getService(baseModel)
val (protocol, generator) = ClientProtocolLoader(
@@ -85,7 +98,7 @@ class ClientCodegenVisitor(
model = codegenDecorator.transformModel(untransformedService, baseModel)
// the model transformer _might_ change the service shape
val service = settings.getService(model)
- symbolProvider = RustClientCodegenPlugin.baseSymbolProvider(model, service, symbolVisitorConfig)
+ symbolProvider = RustClientCodegenPlugin.baseSymbolProvider(settings, model, service, rustSymbolProviderConfig)
codegenContext = ClientCodegenContext(model, symbolProvider, service, protocol, settings, codegenDecorator)
@@ -108,14 +121,14 @@ class ClientCodegenVisitor(
// Add errors attached at the service level to the models
.let { ModelTransformer.create().copyServiceErrorsToOperations(it, settings.getService(it)) }
// Add `Box` to recursive shapes as necessary
- .let(RecursiveShapeBoxer::transform)
+ .let(RecursiveShapeBoxer()::transform)
// Normalize the `message` field on errors when enabled in settings (default: true)
.letIf(settings.codegenConfig.addMessageToErrors, AddErrorMessage::transform)
// NormalizeOperations by ensuring every operation has an input & output shape
.let(OperationNormalizer::transform)
// Drop unsupported event stream operations from the model
.let { RemoveEventStreamOperations.transform(it, settings) }
- // - Normalize event stream operations
+ // Normalize event stream operations
.let(EventStreamNormalizer::transform)
/**
@@ -177,6 +190,29 @@ class ClientCodegenVisitor(
override fun getDefault(shape: Shape?) {
}
+ // TODO(CrateReorganization): Remove this function when cleaning up `enableNewCrateOrganizationScheme`
+ private fun RustCrate.maybeInPrivateModuleWithReexport(
+ privateModule: RustModule.LeafModule,
+ symbol: Symbol,
+ writer: Writable,
+ ) {
+ if (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ inPrivateModuleWithReexport(privateModule, symbol, writer)
+ } else {
+ withModule(symbol.module(), writer)
+ }
+ }
+
+ private fun privateModule(shape: Shape): RustModule.LeafModule =
+ RustModule.private(privateModuleName(shape), parent = symbolProvider.moduleForShape(shape))
+
+ private fun privateModuleName(shape: Shape): String =
+ shape.contextName(codegenContext.serviceShape).let(this::privateModuleName)
+
+ private fun privateModuleName(name: String): String =
+ // Add the underscore to avoid colliding with public module names
+ "_" + RustReservedWords.escapeIfNeeded(name.toSnakeCase(), EscapeFor.ModuleName)
+
/**
* Structure Shape Visitor
*
@@ -187,17 +223,50 @@ class ClientCodegenVisitor(
* This function _does not_ generate any serializers
*/
override fun structureShape(shape: StructureShape) {
- logger.fine("generating a structure...")
- rustCrate.useShapeWriter(shape) {
- StructureGenerator(model, symbolProvider, this, shape).render()
- if (!shape.hasTrait()) {
- val builderGenerator = BuilderGenerator(codegenContext.model, codegenContext.symbolProvider, shape)
- builderGenerator.render(this)
- this.implBlock(shape, symbolProvider) {
- builderGenerator.renderConvenienceMethod(this)
+ val (renderStruct, renderBuilder) = when (val errorTrait = shape.getTrait()) {
+ null -> {
+ val struct: Writable = {
+ StructureGenerator(
+ model,
+ symbolProvider,
+ this,
+ shape,
+ codegenDecorator.structureCustomizations(codegenContext, emptyList()),
+ ).render()
+
+ implBlock(symbolProvider.toSymbol(shape)) {
+ BuilderGenerator.renderConvenienceMethod(this, symbolProvider, shape)
+ }
}
+ val builder: Writable = {
+ BuilderGenerator(
+ codegenContext.model,
+ codegenContext.symbolProvider,
+ shape,
+ codegenDecorator.builderCustomizations(codegenContext, emptyList()),
+ ).render(this)
+ }
+ struct to builder
+ }
+ else -> {
+ val errorGenerator = ErrorGenerator(
+ model,
+ symbolProvider,
+ shape,
+ errorTrait,
+ codegenDecorator.errorImplCustomizations(codegenContext, emptyList()),
+ )
+ errorGenerator::renderStruct to errorGenerator::renderBuilder
}
}
+
+ val privateModule = privateModule(shape)
+ rustCrate.maybeInPrivateModuleWithReexport(privateModule, symbolProvider.toSymbol(shape)) {
+ renderStruct(this)
+ }
+ rustCrate.maybeInPrivateModuleWithReexport(privateModule, symbolProvider.symbolForBuilder(shape)) {
+ renderBuilder(this)
+ }
}
/**
@@ -206,9 +275,10 @@ class ClientCodegenVisitor(
* Although raw strings require no code generation, enums are actually `EnumTrait` applied to string shapes.
*/
override fun stringShape(shape: StringShape) {
- shape.getTrait()?.also { enum ->
- rustCrate.useShapeWriter(shape) {
- EnumGenerator(model, symbolProvider, this, shape, enum).render()
+ if (shape.hasTrait()) {
+ val privateModule = privateModule(shape)
+ rustCrate.maybeInPrivateModuleWithReexport(privateModule, symbolProvider.toSymbol(shape)) {
+ ClientEnumGenerator(codegenContext, shape).render(this)
}
}
}
@@ -221,17 +291,17 @@ class ClientCodegenVisitor(
* Note: this does not generate serializers
*/
override fun unionShape(shape: UnionShape) {
- rustCrate.useShapeWriter(shape) {
+ rustCrate.maybeInPrivateModuleWithReexport(privateModule(shape), symbolProvider.toSymbol(shape)) {
UnionGenerator(model, symbolProvider, this, shape, renderUnknownVariant = true).render()
}
if (shape.isEventStream()) {
- rustCrate.withModule(RustModule.Error) {
- val symbol = symbolProvider.toSymbol(shape)
- val errors = shape.eventStreamErrors()
- .map { model.expectShape(it.asMemberShape().get().target, StructureShape::class.java) }
- val errorSymbol = shape.eventStreamErrorSymbol(symbolProvider)
- OperationErrorGenerator(model, symbolProvider, symbol, errors)
- .renderErrors(this, errorSymbol, symbol)
+ rustCrate.withModule(ClientRustModule.Error) {
+ OperationErrorGenerator(
+ model,
+ symbolProvider,
+ shape,
+ codegenDecorator.errorCustomizations(codegenContext, emptyList()),
+ ).render(this)
}
}
}
@@ -240,13 +310,12 @@ class ClientCodegenVisitor(
* Generate errors for operation shapes
*/
override fun operationShape(shape: OperationShape) {
- rustCrate.withModule(RustModule.Error) {
- val operationSymbol = symbolProvider.toSymbol(shape)
+ rustCrate.withModule(symbolProvider.moduleForOperationError(shape)) {
OperationErrorGenerator(
model,
symbolProvider,
- operationSymbol,
- shape.operationErrors(model).map { it.asStructureShape().get() },
+ shape,
+ codegenDecorator.errorCustomizations(codegenContext, emptyList()),
).render(this)
}
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustModule.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustModule.kt
new file mode 100644
index 00000000000..895b2c1ff4a
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustModule.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy
+
+import software.amazon.smithy.codegen.core.Symbol
+import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.model.shapes.Shape
+import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.model.shapes.UnionShape
+import software.amazon.smithy.model.traits.ErrorTrait
+import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
+import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
+import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
+import software.amazon.smithy.rust.codegen.core.smithy.ModuleProvider
+import software.amazon.smithy.rust.codegen.core.smithy.ModuleProviderContext
+import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
+import software.amazon.smithy.rust.codegen.core.smithy.contextName
+import software.amazon.smithy.rust.codegen.core.smithy.module
+import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait
+import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait
+import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE
+import software.amazon.smithy.rust.codegen.core.util.getTrait
+import software.amazon.smithy.rust.codegen.core.util.hasTrait
+import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
+
+/**
+ * Modules for code generated client crates.
+ */
+object ClientRustModule {
+ /** crate */
+ val root = RustModule.LibRs
+
+ /** crate::client */
+ val client = Client.self
+ object Client {
+ /** crate::client */
+ val self = RustModule.public("client", "Client and fluent builders for calling the service.")
+
+ /** crate::client::customize */
+ val customize = RustModule.public("customize", parent = self, documentation = "Operation customization and supporting types")
+ }
+
+ val Config = RustModule.public("config", documentation = "Configuration for the service.")
+ val Error = RustModule.public("error", documentation = "All error types that operations can return. Documentation on these types is copied from the model.")
+ val Operation = RustModule.public("operation", documentation = "All operations that this crate can perform.")
+ val Meta = RustModule.public("meta", documentation = "Information about this crate.")
+ val Input = RustModule.public("input", documentation = "Input structures for operations. Documentation on these types is copied from the model.")
+ val Output = RustModule.public("output", documentation = "Output structures for operations. Documentation on these types is copied from the model.")
+ val Primitives = RustModule.public("primitives", documentation = "Data primitives referenced by other data types.")
+
+ /** crate::types */
+ val types = Types.self
+ object Types {
+ /** crate::types */
+ val self = RustModule.public("types", documentation = "Data primitives referenced by other data types.")
+
+ /** crate::types::error */
+ val Error = RustModule.public("error", parent = self, documentation = "All error types that operations can return. Documentation on these types is copied from the model.")
+ }
+
+ // TODO(CrateReorganization): Remove this module when cleaning up `enableNewCrateOrganizationScheme`
+ val Model = RustModule.public("model", documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.")
+}
+
+object ClientModuleProvider : ModuleProvider {
+ override fun moduleForShape(context: ModuleProviderContext, shape: Shape): RustModule.LeafModule = when (shape) {
+ is OperationShape -> perOperationModule(context, shape)
+ is StructureShape -> when {
+ shape.hasTrait() -> ClientRustModule.Types.Error
+ shape.hasTrait() -> perOperationModule(context, shape)
+ shape.hasTrait() -> perOperationModule(context, shape)
+ else -> ClientRustModule.types
+ }
+
+ else -> ClientRustModule.types
+ }
+
+ override fun moduleForOperationError(
+ context: ModuleProviderContext,
+ operation: OperationShape,
+ ): RustModule.LeafModule = perOperationModule(context, operation)
+
+ override fun moduleForEventStreamError(
+ context: ModuleProviderContext,
+ eventStream: UnionShape,
+ ): RustModule.LeafModule = ClientRustModule.Error
+
+ override fun moduleForBuilder(context: ModuleProviderContext, shape: Shape, symbol: Symbol): RustModule.LeafModule =
+ RustModule.public("builders", parent = symbol.module(), documentation = "Builders")
+
+ private fun Shape.findOperation(model: Model): OperationShape {
+ val inputTrait = getTrait()
+ val outputTrait = getTrait()
+ return when {
+ this is OperationShape -> this
+ inputTrait != null -> model.expectShape(inputTrait.operation, OperationShape::class.java)
+ outputTrait != null -> model.expectShape(outputTrait.operation, OperationShape::class.java)
+ else -> UNREACHABLE("this is only called with compatible shapes")
+ }
+ }
+
+ private fun perOperationModule(context: ModuleProviderContext, shape: Shape): RustModule.LeafModule {
+ val operationShape = shape.findOperation(context.model)
+ val contextName = operationShape.contextName(context.serviceShape)
+ val operationModuleName =
+ RustReservedWords.escapeIfNeeded(contextName.toSnakeCase())
+ return RustModule.public(
+ operationModuleName,
+ parent = ClientRustModule.Operation,
+ documentation = "Types for the `$contextName` operation.",
+ )
+ }
+}
+
+// TODO(CrateReorganization): Remove this provider
+object OldModuleSchemeClientModuleProvider : ModuleProvider {
+ override fun moduleForShape(context: ModuleProviderContext, shape: Shape): RustModule.LeafModule = when (shape) {
+ is OperationShape -> ClientRustModule.Operation
+ is StructureShape -> when {
+ shape.hasTrait() -> ClientRustModule.Error
+ shape.hasTrait() -> ClientRustModule.Input
+ shape.hasTrait() -> ClientRustModule.Output
+ else -> ClientRustModule.Model
+ }
+
+ else -> ClientRustModule.Model
+ }
+
+ override fun moduleForOperationError(
+ context: ModuleProviderContext,
+ operation: OperationShape,
+ ): RustModule.LeafModule = ClientRustModule.Error
+
+ override fun moduleForEventStreamError(
+ context: ModuleProviderContext,
+ eventStream: UnionShape,
+ ): RustModule.LeafModule = ClientRustModule.Error
+
+ override fun moduleForBuilder(context: ModuleProviderContext, shape: Shape, symbol: Symbol): RustModule.LeafModule {
+ val builderNamespace = RustReservedWords.escapeIfNeeded(symbol.name.toSnakeCase())
+ return RustModule.new(
+ builderNamespace,
+ visibility = Visibility.PUBLIC,
+ parent = symbol.module(),
+ inline = true,
+ documentation = "See [`${symbol.name}`](${symbol.module().fullyQualifiedPath()}::${symbol.name}).",
+ )
+ }
+}
+
+// TODO(CrateReorganization): Remove when cleaning up `enableNewCrateOrganizationScheme`
+fun ClientCodegenContext.featureGatedConfigModule() = when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> ClientRustModule.Config
+ else -> ClientRustModule.root
+}
+
+// TODO(CrateReorganization): Remove when cleaning up `enableNewCrateOrganizationScheme`
+fun ClientCodegenContext.featureGatedCustomizeModule() = when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> ClientRustModule.Client.customize
+ else -> RustModule.public(
+ "customize",
+ "Operation customization and supporting types",
+ parent = ClientRustModule.Operation,
+ )
+}
+
+// TODO(CrateReorganization): Remove when cleaning up `enableNewCrateOrganizationScheme`
+fun ClientCodegenContext.featureGatedMetaModule() = when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> ClientRustModule.Meta
+ else -> ClientRustModule.root
+}
+
+// TODO(CrateReorganization): Remove when cleaning up `enableNewCrateOrganizationScheme`
+fun ClientCodegenContext.featureGatedPaginatorModule(symbolProvider: RustSymbolProvider, operation: OperationShape) =
+ when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> RustModule.public(
+ "paginator",
+ parent = symbolProvider.moduleForShape(operation),
+ documentation = "Paginator for this operation",
+ )
+ else -> RustModule.public("paginator", "Paginators for the service")
+ }
+
+// TODO(CrateReorganization): Remove when cleaning up `enableNewCrateOrganizationScheme`
+fun ClientCodegenContext.featureGatedPrimitivesModule() = when (settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> ClientRustModule.Primitives
+ else -> ClientRustModule.types
+}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt
index 5fb6eb1d1bf..a33fa4f9e7d 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt
@@ -86,6 +86,8 @@ data class ClientCodegenConfig(
val addMessageToErrors: Boolean = defaultAddMessageToErrors,
// TODO(EventStream): [CLEANUP] Remove this property when turning on Event Stream for all services
val eventStreamAllowList: Set = defaultEventStreamAllowList,
+ // TODO(CrateReorganization): Remove this once we commit to the breaking change
+ val enableNewCrateOrganizationScheme: Boolean = defaultEnableNewCrateOrganizationScheme,
) : CoreCodegenConfig(
formatTimeoutSeconds, debugMode,
) {
@@ -94,6 +96,7 @@ data class ClientCodegenConfig(
private const val defaultIncludeFluentClient = true
private const val defaultAddMessageToErrors = true
private val defaultEventStreamAllowList: Set = emptySet()
+ private const val defaultEnableNewCrateOrganizationScheme = false
fun fromCodegenConfigAndNode(coreCodegenConfig: CoreCodegenConfig, node: Optional) =
if (node.isPresent) {
@@ -106,12 +109,14 @@ data class ClientCodegenConfig(
renameExceptions = node.get().getBooleanMemberOrDefault("renameErrors", defaultRenameExceptions),
includeFluentClient = node.get().getBooleanMemberOrDefault("includeFluentClient", defaultIncludeFluentClient),
addMessageToErrors = node.get().getBooleanMemberOrDefault("addMessageToErrors", defaultAddMessageToErrors),
+ enableNewCrateOrganizationScheme = node.get().getBooleanMemberOrDefault("enableNewCrateOrganizationScheme", false),
)
} else {
ClientCodegenConfig(
formatTimeoutSeconds = coreCodegenConfig.formatTimeoutSeconds,
debugMode = coreCodegenConfig.debugMode,
eventStreamAllowList = defaultEventStreamAllowList,
+ enableNewCrateOrganizationScheme = defaultEnableNewCrateOrganizationScheme,
)
}
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt
index 0189a4cb65d..38af758ad04 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/RustClientCodegenPlugin.kt
@@ -9,6 +9,7 @@ import software.amazon.smithy.build.PluginContext
import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
+import software.amazon.smithy.rust.codegen.client.smithy.customizations.ApiKeyAuthDecorator
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ClientCustomizations
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator
@@ -16,16 +17,16 @@ import software.amazon.smithy.rust.codegen.client.smithy.customize.NoOpEventStre
import software.amazon.smithy.rust.codegen.client.smithy.customize.RequiredCustomizations
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointsDecorator
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientDecorator
-import software.amazon.smithy.rust.codegen.client.testutil.DecoratableBuildPlugin
+import software.amazon.smithy.rust.codegen.client.testutil.ClientDecoratableBuildPlugin
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.NonExhaustive
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.BaseSymbolMetadataProvider
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.EventStreamSymbolProvider
+import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig
import software.amazon.smithy.rust.codegen.core.smithy.StreamingShapeMetadataProvider
import software.amazon.smithy.rust.codegen.core.smithy.StreamingShapeSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitor
-import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitorConfig
import java.util.logging.Level
import java.util.logging.Logger
@@ -36,7 +37,7 @@ import java.util.logging.Logger
* `resources/META-INF.services/software.amazon.smithy.build.SmithyBuildPlugin` refers to this class by name which
* enables the smithy-build plugin to invoke `execute` with all Smithy plugin context + models.
*/
-class RustClientCodegenPlugin : DecoratableBuildPlugin() {
+class RustClientCodegenPlugin : ClientDecoratableBuildPlugin() {
override fun getName(): String = "rust-client-codegen"
override fun executeWithDecorator(
@@ -58,6 +59,7 @@ class RustClientCodegenPlugin : DecoratableBuildPlugin() {
FluentClientDecorator(),
EndpointsDecorator(),
NoOpEventStreamSigningDecorator(),
+ ApiKeyAuthDecorator(),
*decorator,
)
@@ -66,24 +68,29 @@ class RustClientCodegenPlugin : DecoratableBuildPlugin() {
}
companion object {
- /** SymbolProvider
+ /**
* When generating code, smithy types need to be converted into Rust types—that is the core role of the symbol provider
*
- * The Symbol provider is composed of a base `SymbolVisitor` which handles the core functionality, then is layered
+ * The Symbol provider is composed of a base [SymbolVisitor] which handles the core functionality, then is layered
* with other symbol providers, documented inline, to handle the full scope of Smithy types.
*/
- fun baseSymbolProvider(model: Model, serviceShape: ServiceShape, symbolVisitorConfig: SymbolVisitorConfig) =
- SymbolVisitor(model, serviceShape = serviceShape, config = symbolVisitorConfig)
+ fun baseSymbolProvider(
+ settings: ClientRustSettings,
+ model: Model,
+ serviceShape: ServiceShape,
+ rustSymbolProviderConfig: RustSymbolProviderConfig,
+ ) =
+ SymbolVisitor(settings, model, serviceShape = serviceShape, config = rustSymbolProviderConfig)
// Generate different types for EventStream shapes (e.g. transcribe streaming)
- .let { EventStreamSymbolProvider(symbolVisitorConfig.runtimeConfig, it, model, CodegenTarget.CLIENT) }
+ .let { EventStreamSymbolProvider(rustSymbolProviderConfig.runtimeConfig, it, CodegenTarget.CLIENT) }
// Generate `ByteStream` instead of `Blob` for streaming binary shapes (e.g. S3 GetObject)
- .let { StreamingShapeSymbolProvider(it, model) }
+ .let { StreamingShapeSymbolProvider(it) }
// Add Rust attributes (like `#[derive(PartialEq)]`) to generated shapes
- .let { BaseSymbolMetadataProvider(it, model, additionalAttributes = listOf(NonExhaustive)) }
+ .let { BaseSymbolMetadataProvider(it, additionalAttributes = listOf(NonExhaustive)) }
// Streaming shapes need different derives (e.g. they cannot derive `PartialEq`)
- .let { StreamingShapeMetadataProvider(it, model) }
+ .let { StreamingShapeMetadataProvider(it) }
// Rename shapes that clash with Rust reserved words & and other SDK specific features e.g. `send()` cannot
// be the name of an operation input
- .let { RustReservedWordSymbolProvider(it, model) }
+ .let { RustReservedWordSymbolProvider(it) }
}
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecorator.kt
new file mode 100644
index 00000000000..0d9f5bde460
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ApiKeyAuthDecorator.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.customizations
+
+import software.amazon.smithy.model.knowledge.ServiceIndex
+import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.model.shapes.ShapeId
+import software.amazon.smithy.model.traits.HttpApiKeyAuthTrait
+import software.amazon.smithy.model.traits.OptionalAuthTrait
+import software.amazon.smithy.model.traits.Trait
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
+import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.rustlang.writable
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
+import software.amazon.smithy.rust.codegen.core.util.expectTrait
+import software.amazon.smithy.rust.codegen.core.util.letIf
+
+/**
+ * Inserts a ApiKeyAuth configuration into the operation
+ */
+class ApiKeyAuthDecorator : ClientCodegenDecorator {
+ override val name: String = "ApiKeyAuth"
+ override val order: Byte = 10
+
+ private fun applies(codegenContext: ClientCodegenContext) =
+ isSupportedApiKeyAuth(codegenContext)
+
+ override fun configCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List {
+ return baseCustomizations.letIf(applies(codegenContext)) { customizations ->
+ customizations + ApiKeyConfigCustomization(codegenContext.runtimeConfig)
+ }
+ }
+
+ override fun operationCustomizations(
+ codegenContext: ClientCodegenContext,
+ operation: OperationShape,
+ baseCustomizations: List,
+ ): List {
+ if (applies(codegenContext) && hasApiKeyAuthScheme(codegenContext, operation)) {
+ val service = codegenContext.serviceShape
+ val authDefinition: HttpApiKeyAuthTrait = service.expectTrait(HttpApiKeyAuthTrait::class.java)
+ return baseCustomizations + ApiKeyOperationCustomization(codegenContext.runtimeConfig, authDefinition)
+ }
+ return baseCustomizations
+ }
+
+ override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ if (applies(codegenContext)) {
+ rustCrate.withModule(ClientRustModule.Config) {
+ rust("pub use #T;", apiKey(codegenContext.runtimeConfig))
+ }
+ }
+ }
+}
+
+/**
+ * Returns if the service supports the httpApiKeyAuth trait.
+ *
+ * @param codegenContext Codegen context that includes the model and service shape
+ * @return if the httpApiKeyAuth trait is used by the service
+ */
+private fun isSupportedApiKeyAuth(codegenContext: ClientCodegenContext): Boolean {
+ return ServiceIndex.of(codegenContext.model).getAuthSchemes(codegenContext.serviceShape).containsKey(HttpApiKeyAuthTrait.ID)
+}
+
+/**
+ * Returns if the service and operation have the httpApiKeyAuthTrait.
+ *
+ * @param codegenContext codegen context that includes the model and service shape
+ * @param operation operation shape
+ * @return if the service and operation have the httpApiKeyAuthTrait
+ */
+private fun hasApiKeyAuthScheme(codegenContext: ClientCodegenContext, operation: OperationShape): Boolean {
+ val auth: Map = ServiceIndex.of(codegenContext.model).getEffectiveAuthSchemes(codegenContext.serviceShape.getId(), operation.getId())
+ return auth.containsKey(HttpApiKeyAuthTrait.ID) && !operation.hasTrait(OptionalAuthTrait.ID)
+}
+
+private class ApiKeyOperationCustomization(private val runtimeConfig: RuntimeConfig, private val authDefinition: HttpApiKeyAuthTrait) : OperationCustomization() {
+ override fun section(section: OperationSection): Writable = when (section) {
+ is OperationSection.MutateRequest -> writable {
+ rustBlock("if let Some(api_key_config) = ${section.config}.api_key()") {
+ rust(
+ """
+ ${section.request}.properties_mut().insert(api_key_config.clone());
+ let api_key = api_key_config.api_key();
+ """,
+ )
+ val definitionName = authDefinition.getName()
+ if (authDefinition.getIn() == HttpApiKeyAuthTrait.Location.QUERY) {
+ rustTemplate(
+ """
+ let auth_definition = #{http_auth_definition}::query(
+ "$definitionName".to_owned(),
+ );
+ let name = auth_definition.name();
+ let mut query = #{query_writer}::new(${section.request}.http().uri());
+ query.insert(name, api_key);
+ *${section.request}.http_mut().uri_mut() = query.build_uri();
+ """,
+ "http_auth_definition" to
+ RuntimeType.smithyHttpAuth(runtimeConfig).resolve("definition::HttpAuthDefinition"),
+ "query_writer" to RuntimeType.smithyHttp(runtimeConfig).resolve("query_writer::QueryWriter"),
+ )
+ } else {
+ val definitionScheme: String = authDefinition.getScheme()
+ .map { scheme ->
+ "Some(\"" + scheme + "\".to_owned())"
+ }
+ .orElse("None")
+ rustTemplate(
+ """
+ let auth_definition = #{http_auth_definition}::header(
+ "$definitionName".to_owned(),
+ $definitionScheme,
+ );
+ let name = auth_definition.name();
+ let value = match auth_definition.scheme() {
+ Some(value) => format!("{value} {api_key}"),
+ None => api_key.to_owned(),
+ };
+ ${section.request}
+ .http_mut()
+ .headers_mut()
+ .insert(
+ #{http_header}::HeaderName::from_bytes(name.as_bytes()).expect("valid header name for api key auth"),
+ #{http_header}::HeaderValue::from_bytes(value.as_bytes()).expect("valid header value for api key auth")
+ );
+ """,
+ "http_auth_definition" to
+ RuntimeType.smithyHttpAuth(runtimeConfig).resolve("definition::HttpAuthDefinition"),
+ "http_header" to RuntimeType.Http.resolve("header"),
+ )
+ }
+ }
+ }
+ else -> emptySection
+ }
+}
+
+private class ApiKeyConfigCustomization(runtimeConfig: RuntimeConfig) : ConfigCustomization() {
+ private val codegenScope = arrayOf(
+ "ApiKey" to apiKey(runtimeConfig),
+ )
+
+ override fun section(section: ServiceConfig): Writable =
+ when (section) {
+ is ServiceConfig.BuilderStruct -> writable {
+ rustTemplate("api_key: Option<#{ApiKey}>,", *codegenScope)
+ }
+ is ServiceConfig.BuilderImpl -> writable {
+ rustTemplate(
+ """
+ /// Sets the API key that will be used by the client.
+ pub fn api_key(mut self, api_key: #{ApiKey}) -> Self {
+ self.set_api_key(Some(api_key));
+ self
+ }
+
+ /// Sets the API key that will be used by the client.
+ pub fn set_api_key(&mut self, api_key: Option<#{ApiKey}>) -> &mut Self {
+ self.api_key = api_key;
+ self
+ }
+ """,
+ *codegenScope,
+ )
+ }
+ is ServiceConfig.BuilderBuild -> writable {
+ rust("api_key: self.api_key,")
+ }
+ is ServiceConfig.ConfigStruct -> writable {
+ rustTemplate("api_key: Option<#{ApiKey}>,", *codegenScope)
+ }
+ is ServiceConfig.ConfigImpl -> writable {
+ rustTemplate(
+ """
+ /// Returns API key used by the client, if it was provided.
+ pub fn api_key(&self) -> Option<{ApiKey}> {
+ self.api_key.as_ref()
+ }
+ """,
+ *codegenScope,
+ )
+ }
+ else -> emptySection
+ }
+}
+
+private fun apiKey(runtimeConfig: RuntimeConfig) = RuntimeType.smithyHttpAuth(runtimeConfig).resolve("api_key::AuthApiKey")
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientDocsGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientDocsGenerator.kt
index 8b8d419ad0b..835f243df37 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientDocsGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ClientDocsGenerator.kt
@@ -16,7 +16,9 @@ class ClientDocsGenerator : LibRsCustomization() {
return when (section) {
is LibRsSection.ModuleDocumentation -> if (section.subsection == LibRsSection.CrateOrganization) {
crateLayout()
- } else emptySection
+ } else {
+ emptySection
+ }
else -> emptySection
}
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt
index 84a6d5f38a1..a52c4a81d63 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt
@@ -5,9 +5,9 @@
package software.amazon.smithy.rust.codegen.client.smithy.customizations
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
@@ -235,7 +235,7 @@ class ResiliencyConfigCustomization(codegenContext: CodegenContext) : ConfigCust
class ResiliencyReExportCustomization(private val runtimeConfig: RuntimeConfig) {
fun extras(rustCrate: RustCrate) {
- rustCrate.withModule(RustModule.Config) {
+ rustCrate.withModule(ClientRustModule.Config) {
rustTemplate(
"""
pub use #{sleep}::{AsyncSleep, Sleep};
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt
index f4034c1b9ba..c893ff078f6 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt
@@ -11,6 +11,7 @@ import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
import software.amazon.smithy.rust.codegen.core.smithy.customize.CombinedCoreCodegenDecorator
import software.amazon.smithy.rust.codegen.core.smithy.customize.CoreCodegenDecorator
@@ -40,6 +41,14 @@ interface ClientCodegenDecorator : CoreCodegenDecorator {
baseCustomizations: List,
): List = baseCustomizations
+ /**
+ * Hook to customize generated errors.
+ */
+ fun errorCustomizations(
+ codegenContext: ClientCodegenContext,
+ baseCustomizations: List,
+ ): List = baseCustomizations
+
fun protocols(serviceId: ShapeId, currentProtocols: ClientProtocolMap): ClientProtocolMap = currentProtocols
fun endpointCustomizations(codegenContext: ClientCodegenContext): List = listOf()
@@ -72,6 +81,13 @@ open class CombinedClientCodegenDecorator(decorators: List,
+ ): List = combineCustomizations(baseCustomizations) { decorator, customizations ->
+ decorator.errorCustomizations(codegenContext, customizations)
+ }
+
override fun protocols(serviceId: ShapeId, currentProtocols: ClientProtocolMap): ClientProtocolMap =
combineCustomizations(currentProtocols) { decorator, protocolMap ->
decorator.protocols(serviceId, protocolMap)
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt
index f65e042d445..c88af4c2bdb 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt
@@ -7,18 +7,22 @@ package software.amazon.smithy.rust.codegen.client.smithy.customize
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.customizations.EndpointPrefixGenerator
import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpChecksumRequiredGenerator
import software.amazon.smithy.rust.codegen.client.smithy.customizations.HttpVersionListCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.IdempotencyTokenGenerator
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyReExportCustomization
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedMetaModule
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedPrimitivesModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.core.rustlang.Feature
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customizations.AllowLintsCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customizations.CrateVersionCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.customizations.pubUseSmithyTypes
+import software.amazon.smithy.rust.codegen.core.smithy.customizations.pubUseSmithyErrorTypes
+import software.amazon.smithy.rust.codegen.core.smithy.customizations.pubUseSmithyPrimitives
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
@@ -54,7 +58,7 @@ class RequiredCustomizations : ClientCodegenDecorator {
codegenContext: ClientCodegenContext,
baseCustomizations: List,
): List =
- baseCustomizations + CrateVersionCustomization() + AllowLintsCustomization()
+ baseCustomizations + AllowLintsCustomization()
override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
// Add rt-tokio feature for `ByteStream::from_path`
@@ -65,6 +69,22 @@ class RequiredCustomizations : ClientCodegenDecorator {
// Re-export resiliency types
ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(rustCrate)
- pubUseSmithyTypes(codegenContext.runtimeConfig, codegenContext.model, rustCrate)
+ rustCrate.withModule(codegenContext.featureGatedPrimitivesModule()) {
+ pubUseSmithyPrimitives(codegenContext, codegenContext.model)(this)
+ if (!codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ pubUseSmithyErrorTypes(codegenContext)(this)
+ }
+ }
+ if (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ rustCrate.withModule(ClientRustModule.Error) {
+ pubUseSmithyErrorTypes(codegenContext)(this)
+ }
+ }
+
+ codegenContext.featureGatedMetaModule().also { metaModule ->
+ rustCrate.withModule(metaModule) {
+ CrateVersionCustomization.extras(rustCrate, metaModule)
+ }
+ }
}
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextConfigCustomization.kt
similarity index 100%
rename from codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextParamDecorator.kt
rename to codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/ClientContextConfigCustomization.kt
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt
index 4d44c5602cd..1b31dec174c 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointConfigCustomization.kt
@@ -90,7 +90,9 @@ internal class EndpointConfigCustomization(
/// let config = $moduleUseName::Config::builder().endpoint_resolver(prefix_resolver);
/// ```
"""
- } else ""
+ } else {
+ ""
+ }
rustTemplate(
"""
/// Sets the endpoint resolver to use when making requests.
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt
index 1e5059830d1..b2a7bde2921 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt
@@ -13,7 +13,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.symbol
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
-import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
@@ -59,7 +58,7 @@ val EndpointTests = RustModule.new(
documentation = "Generated endpoint tests",
parent = EndpointsModule,
inline = true,
-).copy(rustMetadata = RustMetadata.TestModule)
+).cfgTest()
// stdlib is isolated because it contains code generated names of stdlib functions–we want to ensure we avoid clashing
val EndpointsStdLib = RustModule.private("endpoint_lib", "Endpoints standard library functions")
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt
index 183e25d33e7..c4d6efc3278 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt
@@ -14,7 +14,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustom
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Types
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator
-import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.escape
@@ -48,8 +47,7 @@ internal class EndpointTestGenerator(
"Error" to types.resolveEndpointError,
"Document" to RuntimeType.document(runtimeConfig),
"HashMap" to RuntimeType.HashMap,
- "capture_request" to CargoDependency.smithyClient(runtimeConfig)
- .withFeature("test-util").toType().resolve("test_connection::capture_request"),
+ "capture_request" to RuntimeType.captureRequest(runtimeConfig),
)
private val instantiator = clientInstantiator(codegenContext)
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt
index f0bad1aac2e..b0d45366ed0 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/ExpressionGenerator.kt
@@ -65,7 +65,14 @@ class ExpressionGenerator(
getAttr.path.toList().forEach { part ->
when (part) {
is GetAttr.Part.Key -> rust(".${part.key().rustName()}()")
- is GetAttr.Part.Index -> rust(".get(${part.index()}).cloned()") // we end up with Option<&&T>, we need to get to Option<&T>
+ is GetAttr.Part.Index -> {
+ if (part.index() == 0) {
+ // In this case, `.first()` is more idiomatic and `.get(0)` triggers lint warnings
+ rust(".first().cloned()")
+ } else {
+ rust(".get(${part.index()}).cloned()") // we end up with Option<&&T>, we need to get to Option<&T>
+ }
+ }
}
}
if (ownership == Ownership.Owned && getAttr.type() != Type.bool()) {
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt
index d2d43f28580..2c16a51bb72 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/StdLib.kt
@@ -74,10 +74,9 @@ class AwsPartitionResolver(runtimeConfig: RuntimeConfig, private val partitionsD
)
override fun structFieldInit() = writable {
+ val json = Node.printJson(partitionsDotJson).dq()
rustTemplate(
- """partition_resolver: #{PartitionResolver}::new_from_json(b${
- Node.printJson(partitionsDotJson).dq()
- }).expect("valid JSON")""",
+ """partition_resolver: #{PartitionResolver}::new_from_json(b$json).expect("valid JSON")""",
*codegenScope,
)
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGenerator.kt
new file mode 100644
index 00000000000..a217b0f5f09
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGenerator.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators
+
+import software.amazon.smithy.model.shapes.StringShape
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedPrimitivesModule
+import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
+import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.docs
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.rustlang.writable
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumGenerator
+import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumGeneratorContext
+import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumMemberModel
+import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumType
+import software.amazon.smithy.rust.codegen.core.util.dq
+
+/** Infallible enums have an `Unknown` variant and can't fail to parse */
+data class InfallibleEnumType(
+ val unknownVariantModule: RustModule,
+) : EnumType() {
+ companion object {
+ /** Name of the generated unknown enum member name for enums with named members. */
+ const val UnknownVariant = "Unknown"
+
+ /** Name of the opaque struct that is inner data for the generated [UnknownVariant]. */
+ const val UnknownVariantValue = "UnknownVariantValue"
+ }
+
+ override fun implFromForStr(context: EnumGeneratorContext): Writable = writable {
+ rustTemplate(
+ """
+ impl #{From}<&str> for ${context.enumName} {
+ fn from(s: &str) -> Self {
+ match s {
+ #{matchArms}
+ }
+ }
+ }
+ """,
+ "From" to RuntimeType.From,
+ "matchArms" to writable {
+ context.sortedMembers.forEach { member ->
+ rust("${member.value.dq()} => ${context.enumName}::${member.derivedName()},")
+ }
+ rust(
+ "other => ${context.enumName}::$UnknownVariant(#T(other.to_owned()))",
+ unknownVariantValue(context),
+ )
+ },
+ )
+ }
+
+ override fun implFromStr(context: EnumGeneratorContext): Writable = writable {
+ rust(
+ """
+ impl std::str::FromStr for ${context.enumName} {
+ type Err = std::convert::Infallible;
+
+ fn from_str(s: &str) -> std::result::Result {
+ Ok(${context.enumName}::from(s))
+ }
+ }
+ """,
+ )
+ }
+
+ override fun additionalDocs(context: EnumGeneratorContext): Writable = writable {
+ renderForwardCompatibilityNote(context.enumName, context.sortedMembers, UnknownVariant, UnknownVariantValue)
+ }
+
+ override fun additionalEnumMembers(context: EnumGeneratorContext): Writable = writable {
+ docs("`$UnknownVariant` contains new variants that have been added since this code was generated.")
+ rust("$UnknownVariant(#T)", unknownVariantValue(context))
+ }
+
+ override fun additionalAsStrMatchArms(context: EnumGeneratorContext): Writable = writable {
+ rust("${context.enumName}::$UnknownVariant(value) => value.as_str()")
+ }
+
+ private fun unknownVariantValue(context: EnumGeneratorContext): RuntimeType {
+ return RuntimeType.forInlineFun(UnknownVariantValue, unknownVariantModule) {
+ docs(
+ """
+ Opaque struct used as inner data for the `Unknown` variant defined in enums in
+ the crate
+
+ While this is not intended to be used directly, it is marked as `pub` because it is
+ part of the enums that are public interface.
+ """.trimIndent(),
+ )
+ context.enumMeta.render(this)
+ rust("struct $UnknownVariantValue(pub(crate) String);")
+ rustBlock("impl $UnknownVariantValue") {
+ // The generated as_str is not pub as we need to prevent users from calling it on this opaque struct.
+ rustBlock("pub(crate) fn as_str(&self) -> &str") {
+ rust("&self.0")
+ }
+ }
+ }
+ }
+
+ /**
+ * Generate the rustdoc describing how to write a match expression against a generated enum in a
+ * forward-compatible way.
+ */
+ private fun RustWriter.renderForwardCompatibilityNote(
+ enumName: String, sortedMembers: List,
+ unknownVariant: String, unknownVariantValue: String,
+ ) {
+ docs(
+ """
+ When writing a match expression against `$enumName`, it is important to ensure
+ your code is forward-compatible. That is, if a match arm handles a case for a
+ feature that is supported by the service but has not been represented as an enum
+ variant in a current version of SDK, your code should continue to work when you
+ upgrade SDK to a future version in which the enum does include a variant for that
+ feature.
+ """.trimIndent(),
+ )
+ docs("")
+ docs("Here is an example of how you can make a match expression forward-compatible:")
+ docs("")
+ docs("```text")
+ rust("/// ## let ${enumName.lowercase()} = unimplemented!();")
+ rust("/// match ${enumName.lowercase()} {")
+ sortedMembers.mapNotNull { it.name() }.forEach { member ->
+ rust("/// $enumName::${member.name} => { /* ... */ },")
+ }
+ rust("""/// other @ _ if other.as_str() == "NewFeature" => { /* handles a case for `NewFeature` */ },""")
+ rust("/// _ => { /* ... */ },")
+ rust("/// }")
+ docs("```")
+ docs(
+ """
+ The above code demonstrates that when `${enumName.lowercase()}` represents
+ `NewFeature`, the execution path will lead to the second last match arm,
+ even though the enum does not contain a variant `$enumName::NewFeature`
+ in the current version of SDK. The reason is that the variable `other`,
+ created by the `@` operator, is bound to
+ `$enumName::$unknownVariant($unknownVariantValue("NewFeature".to_owned()))`
+ and calling `as_str` on it yields `"NewFeature"`.
+ This match expression is forward-compatible when executed with a newer
+ version of SDK where the variant `$enumName::NewFeature` is defined.
+ Specifically, when `${enumName.lowercase()}` represents `NewFeature`,
+ the execution path will hit the second last match arm as before by virtue of
+ calling `as_str` on `$enumName::NewFeature` also yielding `"NewFeature"`.
+ """.trimIndent(),
+ )
+ docs("")
+ docs(
+ """
+ Explicitly matching on the `$unknownVariant` variant should
+ be avoided for two reasons:
+ - The inner data `$unknownVariantValue` is opaque, and no further information can be extracted.
+ - It might inadvertently shadow other intended match arms.
+ """.trimIndent(),
+ )
+ }
+}
+
+class ClientEnumGenerator(codegenContext: ClientCodegenContext, shape: StringShape) :
+ EnumGenerator(
+ codegenContext.model,
+ codegenContext.symbolProvider,
+ shape,
+ InfallibleEnumType(codegenContext.featureGatedPrimitivesModule()),
+ )
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGenerator.kt
index 54982643e8b..d87c2d1483e 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGenerator.kt
@@ -8,11 +8,11 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.PaginatedIndex
import software.amazon.smithy.model.shapes.OperationShape
-import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.traits.IdempotencyTokenTrait
import software.amazon.smithy.model.traits.PaginatedTrait
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedPaginatorModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerics
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.render
@@ -20,11 +20,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter
import software.amazon.smithy.rust.codegen.core.rustlang.writable
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
-import software.amazon.smithy.rust.codegen.core.smithy.generators.builderSymbol
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.util.PANIC
import software.amazon.smithy.rust.codegen.core.util.findMemberWithTrait
@@ -40,25 +36,21 @@ fun OperationShape.isPaginated(model: Model) =
.findMemberWithTrait(model) == null
class PaginatorGenerator private constructor(
- private val model: Model,
- private val symbolProvider: RustSymbolProvider,
- service: ServiceShape,
+ codegenContext: ClientCodegenContext,
operation: OperationShape,
private val generics: FluentClientGenerics,
retryClassifier: RuntimeType,
) {
companion object {
fun paginatorType(
- codegenContext: CodegenContext,
+ codegenContext: ClientCodegenContext,
generics: FluentClientGenerics,
operationShape: OperationShape,
retryClassifier: RuntimeType,
): RuntimeType? {
return if (operationShape.isPaginated(codegenContext.model)) {
PaginatorGenerator(
- codegenContext.model,
- codegenContext.symbolProvider,
- codegenContext.serviceShape,
+ codegenContext,
operationShape,
generics,
retryClassifier,
@@ -69,17 +61,19 @@ class PaginatorGenerator private constructor(
}
}
+ private val model = codegenContext.model
+ private val symbolProvider = codegenContext.symbolProvider
+ private val runtimeConfig = codegenContext.runtimeConfig
private val paginatorName = "${operation.id.name.toPascalCase()}Paginator"
- private val runtimeConfig = symbolProvider.config().runtimeConfig
private val idx = PaginatedIndex.of(model)
- private val paginationInfo =
- idx.getPaginationInfo(service, operation).orNull() ?: PANIC("failed to load pagination info")
- private val module = RustModule.public("paginator", "Paginators for the service")
+ private val paginationInfo = idx.getPaginationInfo(codegenContext.serviceShape, operation).orNull()
+ ?: PANIC("failed to load pagination info")
+ private val module = codegenContext.featureGatedPaginatorModule(symbolProvider, operation)
private val inputType = symbolProvider.toSymbol(operation.inputShape(model))
private val outputShape = operation.outputShape(model)
private val outputType = symbolProvider.toSymbol(outputShape)
- private val errorType = operation.errorSymbol(symbolProvider)
+ private val errorType = symbolProvider.symbolForOperationError(operation)
private fun paginatorType(): RuntimeType = RuntimeType.forInlineFun(
paginatorName,
@@ -103,7 +97,7 @@ class PaginatorGenerator private constructor(
"Input" to inputType,
"Output" to outputType,
"Error" to errorType,
- "Builder" to operation.inputShape(model).builderSymbol(symbolProvider),
+ "Builder" to symbolProvider.symbolForBuilder(operation.inputShape(model)),
// SDK Types
"SdkError" to RuntimeType.sdkError(runtimeConfig),
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt
index 6710ac9c3d5..c495c623f5d 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ServiceGenerator.kt
@@ -7,14 +7,14 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators
import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfigGenerator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ServiceErrorGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ProtocolTestGenerator
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ServiceErrorGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.core.util.inputShape
@@ -56,9 +56,13 @@ class ServiceGenerator(
}
}
- ServiceErrorGenerator(clientCodegenContext, operations).render(rustCrate)
+ ServiceErrorGenerator(
+ clientCodegenContext,
+ operations,
+ decorator.errorCustomizations(clientCodegenContext, emptyList()),
+ ).render(rustCrate)
- rustCrate.withModule(RustModule.Config) {
+ rustCrate.withModule(ClientRustModule.Config) {
ServiceConfigGenerator.withBaseBehavior(
clientCodegenContext,
extraCustomizations = decorator.configCustomizations(clientCodegenContext, listOf()),
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt
index 31a86d07ac0..31f2ee8f58b 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt
@@ -5,14 +5,13 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.client
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedCustomizeModule
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.GenericTypeArg
import software.amazon.smithy.rust.codegen.core.rustlang.RustGenerics
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
-import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
@@ -21,20 +20,16 @@ import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
* fluent client builders.
*/
class CustomizableOperationGenerator(
- private val runtimeConfig: RuntimeConfig,
+ private val codegenContext: ClientCodegenContext,
private val generics: FluentClientGenerics,
- private val includeFluentClient: Boolean,
) {
-
- companion object {
- val CustomizeModule = RustModule.public("customize", "Operation customization and supporting types", parent = RustModule.operation(Visibility.PUBLIC))
- }
-
+ private val includeFluentClient = codegenContext.settings.codegenConfig.includeFluentClient
+ private val runtimeConfig = codegenContext.runtimeConfig
private val smithyHttp = CargoDependency.smithyHttp(runtimeConfig).toType()
private val smithyTypes = CargoDependency.smithyTypes(runtimeConfig).toType()
fun render(crate: RustCrate) {
- crate.withModule(CustomizeModule) {
+ crate.withModule(codegenContext.featureGatedCustomizeModule()) {
rustTemplate(
"""
pub use #{Operation};
@@ -67,6 +62,7 @@ class CustomizableOperationGenerator(
"handle_generics_bounds" to handleGenerics.bounds(),
"operation_generics_decl" to operationGenerics.declaration(),
"combined_generics_decl" to combinedGenerics.declaration(),
+ "customize_module" to codegenContext.featureGatedCustomizeModule(),
)
writer.rustTemplate(
@@ -81,7 +77,7 @@ class CustomizableOperationGenerator(
/// A wrapper type for [`Operation`](aws_smithy_http::operation::Operation)s that allows for
/// customization of the operation before it is sent. A `CustomizableOperation` may be sent
- /// by calling its [`.send()`][crate::operation::customize::CustomizableOperation::send] method.
+ /// by calling its [`.send()`][#{customize_module}::CustomizableOperation::send] method.
##[derive(Debug)]
pub struct CustomizableOperation#{combined_generics_decl:W} {
pub(crate) handle: Arc,
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt
index b6fced279fb..475c9c490ae 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt
@@ -5,6 +5,7 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.client
+import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
@@ -65,7 +66,7 @@ sealed class FluentClientSection(name: String) : Section(name) {
/** Write custom code into an operation fluent builder's impl block */
data class FluentBuilderImpl(
val operationShape: OperationShape,
- val operationErrorType: RuntimeType,
+ val operationErrorType: Symbol,
) : FluentClientSection("FluentBuilderImpl")
/** Write custom code into the docs */
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt
index d5b8ebd9418..be9c69e0cb8 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt
@@ -13,6 +13,8 @@ import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.DocumentationTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
+import software.amazon.smithy.rust.codegen.client.smithy.featureGatedCustomizeModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.PaginatorGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.isPaginated
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
@@ -21,12 +23,10 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
-import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.asArgumentType
import software.amazon.smithy.rust.codegen.core.rustlang.asOptional
import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape
import software.amazon.smithy.rust.codegen.core.rustlang.docLink
-import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.documentShape
import software.amazon.smithy.rust.codegen.core.rustlang.escape
import software.amazon.smithy.rust.codegen.core.rustlang.normalizeHtml
@@ -43,8 +43,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata
-import software.amazon.smithy.rust.codegen.core.smithy.generators.builderSymbol
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName
import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.util.inputShape
@@ -54,6 +52,7 @@ import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
class FluentClientGenerator(
private val codegenContext: ClientCodegenContext,
+ private val reexportSmithyClientBuilder: Boolean = true,
private val generics: FluentClientGenerics = FlexibleClientGenerics(
connectorDefault = null,
middlewareDefault = null,
@@ -66,11 +65,6 @@ class FluentClientGenerator(
companion object {
fun clientOperationFnName(operationShape: OperationShape, symbolProvider: RustSymbolProvider): String =
RustReservedWords.escapeIfNeeded(symbolProvider.toSymbol(operationShape).name.toSnakeCase())
-
- val clientModule = RustModule.public(
- "client",
- "Client and fluent builders for calling the service.",
- )
}
private val serviceShape = codegenContext.serviceShape
@@ -82,18 +76,29 @@ class FluentClientGenerator(
private val core = FluentClientCore(model)
fun render(crate: RustCrate) {
- crate.withModule(clientModule) {
+ crate.withModule(ClientRustModule.client) {
renderFluentClient(this)
}
- CustomizableOperationGenerator(
- runtimeConfig,
- generics,
- codegenContext.settings.codegenConfig.includeFluentClient,
- ).render(crate)
+ operations.forEach { operation ->
+ crate.withModule(operation.fluentBuilderModule(codegenContext, symbolProvider)) {
+ renderFluentBuilder(operation)
+ }
+ }
+
+ CustomizableOperationGenerator(codegenContext, generics).render(crate)
}
private fun renderFluentClient(writer: RustWriter) {
+ if (!codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme || reexportSmithyClientBuilder) {
+ writer.rustTemplate(
+ """
+ ##[doc(inline)]
+ pub use #{client}::Builder;
+ """,
+ "client" to RuntimeType.smithyClient(runtimeConfig),
+ )
+ }
writer.rustTemplate(
"""
##[derive(Debug)]
@@ -114,9 +119,6 @@ class FluentClientGenerator(
}
}
- ##[doc(inline)]
- pub use #{client}::Builder;
-
impl${generics.inst} From<#{client}::Client#{smithy_inst:W}> for Client${generics.inst} {
fn from(client: #{client}::Client#{smithy_inst:W}) -> Self {
Self::with_config(client, crate::Config::builder().build())
@@ -144,15 +146,15 @@ class FluentClientGenerator(
"smithy_inst" to generics.smithyInst,
"client" to RuntimeType.smithyClient(runtimeConfig),
"client_docs" to writable
- {
- customizations.forEach {
- it.section(
- FluentClientSection.FluentClientDocs(
- serviceShape,
- ),
- )(this)
- }
- },
+ {
+ customizations.forEach {
+ it.section(
+ FluentClientSection.FluentClientDocs(
+ serviceShape,
+ ),
+ )(this)
+ }
+ },
)
writer.rustBlockTemplate(
"impl${generics.inst} Client${generics.inst} #{bounds:W}",
@@ -161,19 +163,24 @@ class FluentClientGenerator(
) {
operations.forEach { operation ->
val name = symbolProvider.toSymbol(operation).name
- val fullPath = operation.fullyQualifiedFluentBuilder(symbolProvider)
+ val fullPath = operation.fullyQualifiedFluentBuilder(codegenContext, symbolProvider)
val maybePaginated = if (operation.isPaginated(model)) {
"\n/// This operation supports pagination; See [`into_paginator()`]($fullPath::into_paginator)."
- } else ""
+ } else {
+ ""
+ }
val output = operation.outputShape(model)
val operationOk = symbolProvider.toSymbol(output)
- val operationErr = operation.errorSymbol(symbolProvider).toSymbol()
+ val operationErr = symbolProvider.symbolForOperationError(operation)
- val inputFieldsBody =
- generateOperationShapeDocs(writer, symbolProvider, operation, model).joinToString("\n") {
- "/// - $it"
- }
+ val inputFieldsBody = generateOperationShapeDocs(
+ writer,
+ codegenContext,
+ symbolProvider,
+ operation,
+ model,
+ ).joinToString("\n") { "/// - $it" }
val inputFieldsHead = if (inputFieldsBody.isNotEmpty()) {
"The fluent builder is configurable:"
@@ -203,155 +210,147 @@ class FluentClientGenerator(
""",
)
- writer.rust(
+ // Write a deprecation notice if this operation is deprecated.
+ writer.deprecatedShape(operation)
+
+ writer.rustTemplate(
"""
- pub fn ${
- clientOperationFnName(
- operation,
- symbolProvider,
- )
- }(&self) -> fluent_builders::$name${generics.inst} {
- fluent_builders::$name::new(self.handle.clone())
+ pub fn #{fnName}(&self) -> #{FluentBuilder}${generics.inst} {
+ #{FluentBuilder}::new(self.handle.clone())
}
""",
+ "fnName" to writable { rust(clientOperationFnName(operation, symbolProvider)) },
+ "FluentBuilder" to operation.fluentBuilderType(codegenContext, symbolProvider),
)
}
}
- writer.withInlineModule(RustModule.new("fluent_builders", visibility = Visibility.PUBLIC, inline = true)) {
- docs(
+ }
+
+ private fun RustWriter.renderFluentBuilder(operation: OperationShape) {
+ val operationSymbol = symbolProvider.toSymbol(operation)
+ val input = operation.inputShape(model)
+ val baseDerives = symbolProvider.toSymbol(input).expectRustMetadata().derives
+ // Filter out any derive that isn't Clone. Then add a Debug derive
+ val derives = baseDerives.filter { it == RuntimeType.Clone } + RuntimeType.Debug
+ rust(
+ """
+ /// Fluent builder constructing a request to `${operationSymbol.name}`.
+ ///
+ """,
+ )
+
+ val builderName = operation.fluentBuilderType(codegenContext, symbolProvider).name
+ documentShape(operation, model, autoSuppressMissingDocs = false)
+ deprecatedShape(operation)
+ Attribute(derive(derives.toSet())).render(this)
+ rustTemplate(
+ """
+ pub struct $builderName#{generics:W} {
+ handle: std::sync::Arc,
+ inner: #{Inner}
+ }
+ """,
+ "Inner" to symbolProvider.symbolForBuilder(input),
+ "client" to RuntimeType.smithyClient(runtimeConfig),
+ "generics" to generics.decl,
+ "operation" to operationSymbol,
+ )
+
+ rustBlockTemplate(
+ "impl${generics.inst} $builderName${generics.inst} #{bounds:W}",
+ "client" to RuntimeType.smithyClient(runtimeConfig),
+ "bounds" to generics.bounds,
+ ) {
+ val outputType = symbolProvider.toSymbol(operation.outputShape(model))
+ val errorType = symbolProvider.symbolForOperationError(operation)
+
+ // Have to use fully-qualified result here or else it could conflict with an op named Result
+ rustTemplate(
"""
- Utilities to ergonomically construct a request to the service.
+ /// Creates a new `${operationSymbol.name}`.
+ pub(crate) fn new(handle: std::sync::Arc) -> Self {
+ Self { handle, inner: Default::default() }
+ }
- Fluent builders are created through the [`Client`](crate::client::Client) by calling
- one if its operation methods. After parameters are set using the builder methods,
- the `send` method can be called to initiate the request.
- """.trim(),
- newlinePrefix = "//! ",
- )
- operations.forEach { operation ->
- val operationSymbol = symbolProvider.toSymbol(operation)
- val input = operation.inputShape(model)
- val baseDerives = symbolProvider.toSymbol(input).expectRustMetadata().derives
- // Filter out any derive that isn't Clone. Then add a Debug derive
- val derives = baseDerives.filter { it == RuntimeType.Clone } + RuntimeType.Debug
- rust(
- """
- /// Fluent builder constructing a request to `${operationSymbol.name}`.
- ///
- """,
- )
+ /// Consume this builder, creating a customizable operation that can be modified before being
+ /// sent. The operation's inner [http::Request] can be modified as well.
+ pub async fn customize(self) -> std::result::Result<
+ #{CustomizableOperation}#{customizable_op_type_params:W},
+ #{SdkError}<#{OperationError}>
+ > #{send_bounds:W} {
+ let handle = self.handle.clone();
+ let operation = self.inner.build().map_err(#{SdkError}::construction_failure)?
+ .make_operation(&handle.conf)
+ .await
+ .map_err(#{SdkError}::construction_failure)?;
+ Ok(#{CustomizableOperation} { handle, operation })
+ }
- documentShape(operation, model, autoSuppressMissingDocs = false)
- deprecatedShape(operation)
- Attribute(derive(derives.toSet())).render(this)
+ /// Sends the request and returns the response.
+ ///
+ /// If an error occurs, an `SdkError` will be returned with additional details that
+ /// can be matched against.
+ ///
+ /// By default, any retryable failures will be retried twice. Retry behavior
+ /// is configurable with the [RetryConfig](aws_smithy_types::retry::RetryConfig), which can be
+ /// set when configuring the client.
+ pub async fn send(self) -> std::result::Result<#{OperationOutput}, #{SdkError}<#{OperationError}>>
+ #{send_bounds:W} {
+ let op = self.inner.build().map_err(#{SdkError}::construction_failure)?
+ .make_operation(&self.handle.conf)
+ .await
+ .map_err(#{SdkError}::construction_failure)?;
+ self.handle.client.call(op).await
+ }
+ """,
+ "CustomizableOperation" to codegenContext.featureGatedCustomizeModule().toType()
+ .resolve("CustomizableOperation"),
+ "ClassifyRetry" to RuntimeType.classifyRetry(runtimeConfig),
+ "OperationError" to errorType,
+ "OperationOutput" to outputType,
+ "SdkError" to RuntimeType.sdkError(runtimeConfig),
+ "SdkSuccess" to RuntimeType.sdkSuccess(runtimeConfig),
+ "send_bounds" to generics.sendBounds(operationSymbol, outputType, errorType, retryClassifier),
+ "customizable_op_type_params" to rustTypeParameters(
+ symbolProvider.toSymbol(operation),
+ retryClassifier,
+ generics.toRustGenerics(),
+ ),
+ )
+ PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryClassifier)?.also { paginatorType ->
rustTemplate(
"""
- pub struct ${operationSymbol.name}#{generics:W} {
- handle: std::sync::Arc,
- inner: #{Inner}
+ /// Create a paginator for this request
+ ///
+ /// Paginators are used by calling [`send().await`](#{Paginator}::send) which returns a `Stream`.
+ pub fn into_paginator(self) -> #{Paginator}${generics.inst} {
+ #{Paginator}::new(self.handle, self.inner)
}
""",
- "Inner" to input.builderSymbol(symbolProvider),
- "client" to RuntimeType.smithyClient(runtimeConfig),
- "generics" to generics.decl,
- "operation" to operationSymbol,
+ "Paginator" to paginatorType,
)
-
- rustBlockTemplate(
- "impl${generics.inst} ${operationSymbol.name}${generics.inst} #{bounds:W}",
- "client" to RuntimeType.smithyClient(runtimeConfig),
- "bounds" to generics.bounds,
- ) {
- val outputType = symbolProvider.toSymbol(operation.outputShape(model))
- val errorType = operation.errorSymbol(symbolProvider)
-
- // Have to use fully-qualified result here or else it could conflict with an op named Result
- rustTemplate(
- """
- /// Creates a new `${operationSymbol.name}`.
- pub(crate) fn new(handle: std::sync::Arc) -> Self {
- Self { handle, inner: Default::default() }
- }
-
- /// Consume this builder, creating a customizable operation that can be modified before being
- /// sent. The operation's inner [http::Request] can be modified as well.
- pub async fn customize(self) -> std::result::Result<
- crate::operation::customize::CustomizableOperation#{customizable_op_type_params:W},
- #{SdkError}<#{OperationError}>
- > #{send_bounds:W} {
- let handle = self.handle.clone();
- let operation = self.inner.build().map_err(#{SdkError}::construction_failure)?
- .make_operation(&handle.conf)
- .await
- .map_err(#{SdkError}::construction_failure)?;
- Ok(crate::operation::customize::CustomizableOperation { handle, operation })
- }
-
- /// Sends the request and returns the response.
- ///
- /// If an error occurs, an `SdkError` will be returned with additional details that
- /// can be matched against.
- ///
- /// By default, any retryable failures will be retried twice. Retry behavior
- /// is configurable with the [RetryConfig](aws_smithy_types::retry::RetryConfig), which can be
- /// set when configuring the client.
- pub async fn send(self) -> std::result::Result<#{OperationOutput}, #{SdkError}<#{OperationError}>>
- #{send_bounds:W} {
- let op = self.inner.build().map_err(#{SdkError}::construction_failure)?
- .make_operation(&self.handle.conf)
- .await
- .map_err(#{SdkError}::construction_failure)?;
- self.handle.client.call(op).await
- }
- """,
- "ClassifyRetry" to RuntimeType.classifyRetry(runtimeConfig),
- "OperationError" to errorType,
- "OperationOutput" to outputType,
- "SdkError" to RuntimeType.sdkError(runtimeConfig),
- "SdkSuccess" to RuntimeType.sdkSuccess(runtimeConfig),
- "send_bounds" to generics.sendBounds(operationSymbol, outputType, errorType, retryClassifier),
- "customizable_op_type_params" to rustTypeParameters(
- symbolProvider.toSymbol(operation),
- retryClassifier,
- generics.toRustGenerics(),
- ),
- )
- PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryClassifier)?.also { paginatorType ->
- rustTemplate(
- """
- /// Create a paginator for this request
- ///
- /// Paginators are used by calling [`send().await`](#{Paginator}::send) which returns a `Stream`.
- pub fn into_paginator(self) -> #{Paginator}${generics.inst} {
- #{Paginator}::new(self.handle, self.inner)
- }
- """,
- "Paginator" to paginatorType,
- )
- }
- writeCustomizations(
- customizations,
- FluentClientSection.FluentBuilderImpl(
- operation,
- operation.errorSymbol(symbolProvider),
- ),
- )
- input.members().forEach { member ->
- val memberName = symbolProvider.toMemberName(member)
- // All fields in the builder are optional
- val memberSymbol = symbolProvider.toSymbol(member)
- val outerType = memberSymbol.rustType()
- when (val coreType = outerType.stripOuter()) {
- is RustType.Vec -> with(core) { renderVecHelper(member, memberName, coreType) }
- is RustType.HashMap -> with(core) { renderMapHelper(member, memberName, coreType) }
- else -> with(core) { renderInputHelper(member, memberName, coreType) }
- }
- // pure setter
- val setterName = member.setterName()
- val optionalInputType = outerType.asOptional()
- with(core) { renderInputHelper(member, setterName, optionalInputType) }
- }
+ }
+ writeCustomizations(
+ customizations,
+ FluentClientSection.FluentBuilderImpl(
+ operation,
+ symbolProvider.symbolForOperationError(operation),
+ ),
+ )
+ input.members().forEach { member ->
+ val memberName = symbolProvider.toMemberName(member)
+ // All fields in the builder are optional
+ val memberSymbol = symbolProvider.toSymbol(member)
+ val outerType = memberSymbol.rustType()
+ when (val coreType = outerType.stripOuter()) {
+ is RustType.Vec -> with(core) { renderVecHelper(member, memberName, coreType) }
+ is RustType.HashMap -> with(core) { renderMapHelper(member, memberName, coreType) }
+ else -> with(core) { renderInputHelper(member, memberName, coreType) }
}
+ // pure setter
+ val setterName = member.setterName()
+ val optionalInputType = outerType.asOptional()
+ with(core) { renderInputHelper(member, setterName, optionalInputType) }
}
}
}
@@ -365,12 +364,13 @@ class FluentClientGenerator(
*/
private fun generateOperationShapeDocs(
writer: RustWriter,
- symbolProvider: SymbolProvider,
+ codegenContext: ClientCodegenContext,
+ symbolProvider: RustSymbolProvider,
operation: OperationShape,
model: Model,
): List {
val input = operation.inputShape(model)
- val fluentBuilderFullyQualifiedName = operation.fullyQualifiedFluentBuilder(symbolProvider)
+ val fluentBuilderFullyQualifiedName = operation.fullyQualifiedFluentBuilder(codegenContext, symbolProvider)
return input.members().map { memberShape ->
val builderInputDoc = memberShape.asFluentBuilderInputDoc(symbolProvider)
val builderInputLink = docLink("$fluentBuilderFullyQualifiedName::${symbolProvider.toMemberName(memberShape)}")
@@ -413,17 +413,46 @@ private fun generateShapeMemberDocs(
}
}
+private fun OperationShape.fluentBuilderModule(
+ codegenContext: ClientCodegenContext,
+ symbolProvider: RustSymbolProvider,
+) = when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> symbolProvider.moduleForBuilder(this)
+ else -> RustModule.public(
+ "fluent_builders",
+ parent = ClientRustModule.client,
+ documentation = """
+ Utilities to ergonomically construct a request to the service.
+
+ Fluent builders are created through the [`Client`](crate::client::Client) by calling
+ one if its operation methods. After parameters are set using the builder methods,
+ the `send` method can be called to initiate the request.
+ """.trimIndent(),
+ )
+}
+
+internal fun OperationShape.fluentBuilderType(
+ codegenContext: ClientCodegenContext,
+ symbolProvider: RustSymbolProvider,
+): RuntimeType = fluentBuilderModule(codegenContext, symbolProvider).toType()
+ .resolve(
+ symbolProvider.toSymbol(this).name +
+ when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> "FluentBuilder"
+ else -> ""
+ },
+ )
+
/**
* Generate a valid fully-qualified Type for a fluent builder e.g.
- * `OperationShape(AssumeRole)` -> `"crate::client::fluent_builders::AssumeRole"`
+ * `OperationShape(AssumeRole)` -> `"crate::operations::assume_role::AssumeRoleFluentBuilder"`
*
* * _NOTE: This function generates the links that appear under **"The fluent builder is configurable:"**_
*/
-private fun OperationShape.fullyQualifiedFluentBuilder(symbolProvider: SymbolProvider): String {
- val operationName = symbolProvider.toSymbol(this).name
-
- return "crate::client::fluent_builders::$operationName"
-}
+private fun OperationShape.fullyQualifiedFluentBuilder(
+ codegenContext: ClientCodegenContext,
+ symbolProvider: RustSymbolProvider,
+): String = fluentBuilderType(codegenContext, symbolProvider).fullyQualifiedName()
/**
* Generate a string that looks like a Rust function pointer for documenting a fluent builder method e.g.
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerics.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerics.kt
index b3229051e69..399085d5e5b 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerics.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerics.kt
@@ -28,7 +28,7 @@ interface FluentClientGenerics {
val bounds: Writable
/** Bounds for generated `send()` functions */
- fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryClassifier: RuntimeType): Writable
+ fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: Symbol, retryClassifier: RuntimeType): Writable
/** Convert this `FluentClientGenerics` into the more general `RustGenerics` */
fun toRustGenerics(): RustGenerics
@@ -70,7 +70,7 @@ data class FlexibleClientGenerics(
}
/** Bounds for generated `send()` functions */
- override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryClassifier: RuntimeType): Writable = writable {
+ override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: Symbol, retryClassifier: RuntimeType): Writable = writable {
rustTemplate(
"""
where
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/IdempotencyTokenProviderCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/IdempotencyTokenProviderCustomization.kt
index 7cda821957e..75b075dc4d5 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/IdempotencyTokenProviderCustomization.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/IdempotencyTokenProviderCustomization.kt
@@ -63,7 +63,9 @@ class IdempotencyTokenProviderCustomization : NamedCustomization(
rust("make_token: self.make_token.unwrap_or_else(#T::default_provider),", RuntimeType.IdempotencyToken)
}
- is ServiceConfig.DefaultForTests -> writable { rust("""${section.configBuilderRef}.set_make_token(Some("00000000-0000-4000-8000-000000000000".into()));""") }
+ is ServiceConfig.DefaultForTests -> writable {
+ rust("""${section.configBuilderRef}.set_make_token(Some("00000000-0000-4000-8000-000000000000".into()));""")
+ }
else -> writable { }
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorCustomization.kt
new file mode 100644
index 00000000000..d275c9b17d4
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorCustomization.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+
+import software.amazon.smithy.codegen.core.Symbol
+import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.customize.Section
+
+/** Error customization sections */
+sealed class ErrorSection(name: String) : Section(name) {
+ /** Use this section to add additional trait implementations to the generated operation errors */
+ data class OperationErrorAdditionalTraitImpls(val errorSymbol: Symbol, val allErrors: List) :
+ ErrorSection("OperationErrorAdditionalTraitImpls")
+
+ /** Use this section to add additional trait implementations to the generated service error */
+ class ServiceErrorAdditionalTraitImpls(val allErrors: List) :
+ ErrorSection("ServiceErrorAdditionalTraitImpls")
+}
+
+/** Customizations for generated errors */
+abstract class ErrorCustomization : NamedCustomization()
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt
new file mode 100644
index 00000000000..7380d475115
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+
+import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.model.traits.ErrorTrait
+import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.rustlang.writable
+import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.errorMetadata
+import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
+import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
+import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderSection
+import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator
+import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureSection
+import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplCustomization
+import software.amazon.smithy.rust.codegen.core.smithy.generators.error.ErrorImplGenerator
+
+class ErrorGenerator(
+ private val model: Model,
+ private val symbolProvider: RustSymbolProvider,
+ private val shape: StructureShape,
+ private val error: ErrorTrait,
+ private val implCustomizations: List,
+) {
+ private val runtimeConfig = symbolProvider.config.runtimeConfig
+ private val symbol = symbolProvider.toSymbol(shape)
+
+ fun renderStruct(writer: RustWriter) {
+ writer.apply {
+ StructureGenerator(
+ model, symbolProvider, this, shape,
+ listOf(
+ object : StructureCustomization() {
+ override fun section(section: StructureSection): Writable = writable {
+ when (section) {
+ is StructureSection.AdditionalFields -> {
+ rust("pub(crate) meta: #T,", errorMetadata(runtimeConfig))
+ }
+
+ is StructureSection.AdditionalDebugFields -> {
+ rust("""${section.formatterName}.field("meta", &self.meta);""")
+ }
+
+ else -> {}
+ }
+ }
+ },
+ ),
+ ).render()
+
+ ErrorImplGenerator(
+ model,
+ symbolProvider,
+ this,
+ shape,
+ error,
+ implCustomizations,
+ ).render(CodegenTarget.CLIENT)
+
+ rustBlock("impl #T for ${symbol.name}", RuntimeType.provideErrorMetadataTrait(runtimeConfig)) {
+ rust("fn meta(&self) -> T { &self.meta }", errorMetadata(runtimeConfig))
+ }
+
+ implBlock(symbol) {
+ BuilderGenerator.renderConvenienceMethod(this, symbolProvider, shape)
+ }
+ }
+ }
+
+ fun renderBuilder(writer: RustWriter) {
+ writer.apply {
+ BuilderGenerator(
+ model, symbolProvider, shape,
+ listOf(
+ object : BuilderCustomization() {
+ override fun section(section: BuilderSection): Writable = writable {
+ when (section) {
+ is BuilderSection.AdditionalFields -> {
+ rust("meta: Option<#T>,", errorMetadata(runtimeConfig))
+ }
+
+ is BuilderSection.AdditionalMethods -> {
+ rustTemplate(
+ """
+ /// Sets error metadata
+ pub fn meta(mut self, meta: #{error_metadata}) -> Self {
+ self.meta = Some(meta);
+ self
+ }
+
+ /// Sets error metadata
+ pub fn set_meta(&mut self, meta: Option<#{error_metadata}>) -> &mut Self {
+ self.meta = meta;
+ self
+ }
+ """,
+ "error_metadata" to errorMetadata(runtimeConfig),
+ )
+ }
+
+ is BuilderSection.AdditionalFieldsInBuild -> {
+ rust("meta: self.meta.unwrap_or_default(),")
+ }
+
+ else -> {}
+ }
+ }
+ },
+ ),
+ ).render(this)
+ }
+ }
+}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGenerator.kt
new file mode 100644
index 00000000000..d9ef055a375
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGenerator.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+
+import software.amazon.smithy.codegen.core.Symbol
+import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.model.shapes.Shape
+import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.model.shapes.UnionShape
+import software.amazon.smithy.model.traits.RetryableTrait
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
+import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
+import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
+import software.amazon.smithy.rust.codegen.core.rustlang.Writable
+import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape
+import software.amazon.smithy.rust.codegen.core.rustlang.docs
+import software.amazon.smithy.rust.codegen.core.rustlang.documentShape
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
+import software.amazon.smithy.rust.codegen.core.rustlang.writable
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.errorMetadata
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.unhandledError
+import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
+import software.amazon.smithy.rust.codegen.core.smithy.customize.Section
+import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
+import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamErrors
+import software.amazon.smithy.rust.codegen.core.smithy.transformers.operationErrors
+import software.amazon.smithy.rust.codegen.core.util.UNREACHABLE
+import software.amazon.smithy.rust.codegen.core.util.dq
+import software.amazon.smithy.rust.codegen.core.util.hasTrait
+import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
+
+/**
+ * Generates a unified error enum for [operation]. [ErrorGenerator] handles generating the individual variants,
+ * but we must still combine those variants into an enum covering all possible errors for a given operation.
+ *
+ * This generator also generates errors for event streams.
+ */
+class OperationErrorGenerator(
+ private val model: Model,
+ private val symbolProvider: RustSymbolProvider,
+ private val operationOrEventStream: Shape,
+ private val customizations: List,
+) {
+ private val runtimeConfig = symbolProvider.config.runtimeConfig
+ private val symbol = symbolProvider.toSymbol(operationOrEventStream)
+ private val errorMetadata = errorMetadata(symbolProvider.config.runtimeConfig)
+ private val createUnhandledError =
+ RuntimeType.smithyHttp(runtimeConfig).resolve("result::CreateUnhandledError")
+
+ private fun operationErrors(): List =
+ (operationOrEventStream as OperationShape).operationErrors(model).map { it.asStructureShape().get() }
+ private fun eventStreamErrors(): List =
+ (operationOrEventStream as UnionShape).eventStreamErrors()
+ .map { model.expectShape(it.asMemberShape().get().target, StructureShape::class.java) }
+
+ fun render(writer: RustWriter) {
+ val (errorSymbol, errors) = when (operationOrEventStream) {
+ is OperationShape -> symbolProvider.symbolForOperationError(operationOrEventStream) to operationErrors()
+ is UnionShape -> symbolProvider.symbolForEventStreamError(operationOrEventStream) to eventStreamErrors()
+ else -> UNREACHABLE("OperationErrorGenerator only supports operation or event stream shapes")
+ }
+
+ val meta = RustMetadata(
+ derives = setOf(RuntimeType.Debug),
+ additionalAttributes = listOf(Attribute.NonExhaustive),
+ visibility = Visibility.PUBLIC,
+ )
+
+ // TODO(deprecated): Remove this temporary alias. This was added so that the compiler
+ // points customers in the right direction when they are upgrading. Unfortunately there's no
+ // way to provide better backwards compatibility on this change.
+ val kindDeprecationMessage = "Operation `*Error/*ErrorKind` types were combined into a single `*Error` enum. " +
+ "The `.kind` field on `*Error` no longer exists and isn't needed anymore (you can just match on the " +
+ "error directly since it's an enum now)."
+ writer.rust(
+ """
+ /// Do not use this.
+ ///
+ /// $kindDeprecationMessage
+ ##[deprecated(note = ${kindDeprecationMessage.dq()})]
+ pub type ${errorSymbol.name}Kind = ${errorSymbol.name};
+ """,
+ )
+
+ writer.rust("/// Error type for the `${errorSymbol.name}` operation.")
+ meta.render(writer)
+ writer.rustBlock("enum ${errorSymbol.name}") {
+ errors.forEach { errorVariant ->
+ documentShape(errorVariant, model)
+ deprecatedShape(errorVariant)
+ val errorVariantSymbol = symbolProvider.toSymbol(errorVariant)
+ write("${errorVariantSymbol.name}(#T),", errorVariantSymbol)
+ }
+ rust(
+ """
+ /// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).
+ Unhandled(#T),
+ """,
+ unhandledError(runtimeConfig),
+ )
+ }
+ writer.rustBlock("impl #T for ${errorSymbol.name}", createUnhandledError) {
+ rustBlock(
+ """
+ fn create_unhandled_error(
+ source: Box,
+ meta: Option<#T>
+ ) -> Self
+ """,
+ errorMetadata,
+ ) {
+ rust(
+ """
+ Self::Unhandled({
+ let mut builder = #T::builder().source(source);
+ builder.set_meta(meta);
+ builder.build()
+ })
+ """,
+ unhandledError(runtimeConfig),
+ )
+ }
+ }
+ writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.Display) {
+ rustBlock("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result") {
+ delegateToVariants(errors) {
+ writable { rust("_inner.fmt(f)") }
+ }
+ }
+ }
+
+ val errorMetadataTrait = RuntimeType.provideErrorMetadataTrait(runtimeConfig)
+ writer.rustBlock("impl #T for ${errorSymbol.name}", errorMetadataTrait) {
+ rustBlock("fn meta(&self) -> T", errorMetadata(runtimeConfig)) {
+ delegateToVariants(errors) {
+ writable { rust("#T::meta(_inner)", errorMetadataTrait) }
+ }
+ }
+ }
+
+ writer.writeCustomizations(customizations, ErrorSection.OperationErrorAdditionalTraitImpls(errorSymbol, errors))
+
+ val retryErrorKindT = RuntimeType.retryErrorKind(symbolProvider.config.runtimeConfig)
+ writer.rustBlock(
+ "impl #T for ${errorSymbol.name}",
+ RuntimeType.provideErrorKind(symbolProvider.config.runtimeConfig),
+ ) {
+ rustBlock("fn code(&self) -> Option<&str>") {
+ rust("#T::code(self)", RuntimeType.provideErrorMetadataTrait(runtimeConfig))
+ }
+
+ rustBlock("fn retryable_error_kind(&self) -> Option<#T>", retryErrorKindT) {
+ val retryableVariants = errors.filter { it.hasTrait() }
+ if (retryableVariants.isEmpty()) {
+ rust("None")
+ } else {
+ rustBlock("match self") {
+ retryableVariants.forEach {
+ val errorVariantSymbol = symbolProvider.toSymbol(it)
+ rust("Self::${errorVariantSymbol.name}(inner) => Some(inner.retryable_error_kind()),")
+ }
+ rust("_ => None")
+ }
+ }
+ }
+ }
+
+ writer.rustBlock("impl ${errorSymbol.name}") {
+ writer.rustTemplate(
+ """
+ /// Creates the `${errorSymbol.name}::Unhandled` variant from any error type.
+ pub fn unhandled(err: impl Into>) -> Self {
+ Self::Unhandled(#{Unhandled}::builder().source(err).build())
+ }
+
+ /// Creates the `${errorSymbol.name}::Unhandled` variant from a `#{error_metadata}`.
+ pub fn generic(err: #{error_metadata}) -> Self {
+ Self::Unhandled(#{Unhandled}::builder().source(err.clone()).meta(err).build())
+ }
+ """,
+ "error_metadata" to errorMetadata,
+ "std_error" to RuntimeType.StdError,
+ "Unhandled" to unhandledError(runtimeConfig),
+ )
+ writer.docs(
+ """
+ Returns error metadata, which includes the error code, message,
+ request ID, and potentially additional information.
+ """,
+ )
+ writer.rustBlock("pub fn meta(&self) -> T", errorMetadata) {
+ rust("use #T;", RuntimeType.provideErrorMetadataTrait(runtimeConfig))
+ rustBlock("match self") {
+ errors.forEach { error ->
+ val errorVariantSymbol = symbolProvider.toSymbol(error)
+ rust("Self::${errorVariantSymbol.name}(e) => e.meta(),")
+ }
+ rust("Self::Unhandled(e) => e.meta(),")
+ }
+ }
+ errors.forEach { error ->
+ val errorVariantSymbol = symbolProvider.toSymbol(error)
+ val fnName = errorVariantSymbol.name.toSnakeCase()
+ writer.rust("/// Returns `true` if the error kind is `${errorSymbol.name}::${errorVariantSymbol.name}`.")
+ writer.rustBlock("pub fn is_$fnName(&self) -> bool") {
+ rust("matches!(self, Self::${errorVariantSymbol.name}(_))")
+ }
+ }
+ }
+
+ writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.StdError) {
+ rustBlock("fn source(&self) -> Option<&(dyn #T + 'static)>", RuntimeType.StdError) {
+ delegateToVariants(errors) {
+ writable {
+ rust("Some(_inner)")
+ }
+ }
+ }
+ }
+ }
+
+ sealed class VariantMatch(name: String) : Section(name) {
+ object Unhandled : VariantMatch("Unhandled")
+ data class Modeled(val symbol: Symbol, val shape: Shape) : VariantMatch("Modeled")
+ }
+
+ /**
+ * Generates code to delegate behavior to the variants, for example:
+ *
+ * ```rust
+ * match self {
+ * Self::InvalidGreeting(_inner) => inner.fmt(f),
+ * Self::ComplexError(_inner) => inner.fmt(f),
+ * Self::FooError(_inner) => inner.fmt(f),
+ * Self::Unhandled(_inner) => _inner.fmt(f),
+ * }
+ * ```
+ *
+ * [handler] is passed an instance of [VariantMatch]—a [writable] should be returned containing the content to be
+ * written for this variant.
+ *
+ * The field will always be bound as `_inner`.
+ */
+ fun RustWriter.delegateToVariants(
+ errors: List,
+ handler: (VariantMatch) -> Writable,
+ ) {
+ rustBlock("match self") {
+ errors.forEach {
+ val errorSymbol = symbolProvider.toSymbol(it)
+ rust("""Self::${errorSymbol.name}(_inner) => """)
+ handler(VariantMatch.Modeled(errorSymbol, it))(this)
+ write(",")
+ }
+ val unhandledHandler = handler(VariantMatch.Unhandled)
+ rustBlock("Self::Unhandled(_inner) =>") {
+ unhandledHandler(this)
+ }
+ }
+ }
+}
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGenerator.kt
similarity index 71%
rename from codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGenerator.kt
rename to codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGenerator.kt
index c03a30b6cca..f461d59e053 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGenerator.kt
@@ -3,8 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package software.amazon.smithy.rust.codegen.core.smithy.generators.error
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.StructureShape
@@ -23,7 +24,9 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.unhandledError
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
import software.amazon.smithy.rust.codegen.core.smithy.transformers.allErrors
import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamErrors
@@ -42,11 +45,17 @@ import software.amazon.smithy.rust.codegen.core.smithy.transformers.eventStreamE
* }
* ```
*/
-class ServiceErrorGenerator(private val codegenContext: CodegenContext, private val operations: List) {
+class ServiceErrorGenerator(
+ private val codegenContext: CodegenContext,
+ private val operations: List,
+ private val customizations: List,
+) {
private val symbolProvider = codegenContext.symbolProvider
private val model = codegenContext.model
- private val allErrors = operations.flatMap { it.allErrors(model) }.map { it.id }.distinctBy { it.getName(codegenContext.serviceShape) }
+ private val allErrors = operations.flatMap {
+ it.allErrors(model)
+ }.map { it.id }.distinctBy { it.getName(codegenContext.serviceShape) }
.map { codegenContext.model.expectShape(it, StructureShape::class.java) }
.sortedBy { it.id.getName(codegenContext.serviceShape) }
@@ -59,7 +68,7 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private
// Every operation error can be converted into service::Error
operations.forEach { operationShape ->
// operation errors
- renderImplFrom(operationShape.errorSymbol(symbolProvider), operationShape.errors)
+ renderImplFrom(symbolProvider.symbolForOperationError(operationShape), operationShape.errors)
}
// event stream errors
operations.map { it.eventStreamErrors(codegenContext.model) }
@@ -67,11 +76,12 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private
.associate { it.key to it.value }
.forEach { (unionShape, errors) ->
renderImplFrom(
- unionShape.eventStreamErrorSymbol(symbolProvider),
+ symbolProvider.symbolForEventStreamError(unionShape),
errors.map { it.id },
)
}
rust("impl #T for Error {}", RuntimeType.StdError)
+ writeCustomizations(customizations, ErrorSection.ServiceErrorAdditionalTraitImpls(allErrors))
}
crate.lib { rust("pub use error_meta::Error;") }
}
@@ -89,7 +99,7 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private
}
}
- private fun RustWriter.renderImplFrom(errorSymbol: RuntimeType, errors: List) {
+ private fun RustWriter.renderImplFrom(errorSymbol: Symbol, errors: List) {
if (errors.isNotEmpty() || CodegenTarget.CLIENT == codegenContext.target) {
val operationErrors = errors.map { model.expectShape(it) }
rustBlock(
@@ -104,25 +114,36 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private
) {
rustBlock("match err") {
rust("#T::ServiceError(context) => Self::from(context.into_err()),", sdkError)
- rust("_ => Error::Unhandled(#T::new(err.into())),", unhandledError())
+ rustTemplate(
+ """
+ _ => Error::Unhandled(
+ #{Unhandled}::builder()
+ .meta(#{ProvideErrorMetadata}::meta(&err).clone())
+ .source(err)
+ .build()
+ ),
+ """,
+ "Unhandled" to unhandledError(codegenContext.runtimeConfig),
+ "ProvideErrorMetadata" to RuntimeType.provideErrorMetadataTrait(codegenContext.runtimeConfig),
+ )
}
}
}
rustBlock("impl From<#T> for Error", errorSymbol) {
rustBlock("fn from(err: #T) -> Self", errorSymbol) {
- rustBlock("match err.kind") {
+ rustBlock("match err") {
operationErrors.forEach { errorShape ->
val errSymbol = symbolProvider.toSymbol(errorShape)
rust(
- "#TKind::${errSymbol.name}(inner) => Error::${errSymbol.name}(inner),",
+ "#T::${errSymbol.name}(inner) => Error::${errSymbol.name}(inner),",
errorSymbol,
)
}
rustTemplate(
- "#{errorSymbol}Kind::Unhandled(inner) => Error::Unhandled(#{unhandled}::new(inner.into())),",
+ "#{errorSymbol}::Unhandled(inner) => Error::Unhandled(inner),",
"errorSymbol" to errorSymbol,
- "unhandled" to unhandledError(),
+ "unhandled" to unhandledError(codegenContext.runtimeConfig),
)
}
}
@@ -143,8 +164,8 @@ class ServiceErrorGenerator(private val codegenContext: CodegenContext, private
val sym = symbolProvider.toSymbol(error)
rust("${sym.name}(#T),", sym)
}
- docs(UNHANDLED_ERROR_DOCS)
- rust("Unhandled(#T)", unhandledError())
+ docs("An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).")
+ rust("Unhandled(#T)", unhandledError(codegenContext.runtimeConfig))
}
}
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt
index 9ec7173d03d..ae725af304a 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt
@@ -5,7 +5,6 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.http
-import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.knowledge.HttpBinding
import software.amazon.smithy.model.knowledge.HttpBindingIndex
import software.amazon.smithy.model.pattern.SmithyPattern
@@ -13,7 +12,6 @@ import software.amazon.smithy.model.shapes.MapShape
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.Shape
-import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.model.traits.HttpTrait
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
@@ -26,7 +24,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.generators.OperationBuildError
-import software.amazon.smithy.rust.codegen.core.smithy.generators.builderSymbol
import software.amazon.smithy.rust.codegen.core.smithy.generators.http.HttpBindingGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError
import software.amazon.smithy.rust.codegen.core.smithy.isOptional
@@ -68,9 +65,8 @@ class RequestBindingGenerator(
private val symbolProvider = codegenContext.symbolProvider
private val runtimeConfig = codegenContext.runtimeConfig
private val httpTrait = protocol.httpBindingResolver.httpTrait(operationShape)
- private fun builderSymbol(shape: StructureShape): Symbol = shape.builderSymbol(symbolProvider)
private val httpBindingGenerator =
- HttpBindingGenerator(protocol, codegenContext, codegenContext.symbolProvider, operationShape, ::builderSymbol)
+ HttpBindingGenerator(protocol, codegenContext, codegenContext.symbolProvider, operationShape)
private val index = HttpBindingIndex.of(model)
private val encoder = RuntimeType.smithyTypes(runtimeConfig).resolve("primitive::Encoder")
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGenerator.kt
index 7becd9df016..911028ea5ac 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGenerator.kt
@@ -7,24 +7,20 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.http
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
-import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.generators.builderSymbol
import software.amazon.smithy.rust.codegen.core.smithy.generators.http.HttpBindingGenerator
import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpBindingDescriptor
import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
class ResponseBindingGenerator(
protocol: Protocol,
- private val codegenContext: CodegenContext,
+ codegenContext: CodegenContext,
operationShape: OperationShape,
) {
- private fun builderSymbol(shape: StructureShape): Symbol = shape.builderSymbol(codegenContext.symbolProvider)
-
private val httpBindingGenerator =
- HttpBindingGenerator(protocol, codegenContext, codegenContext.symbolProvider, operationShape, ::builderSymbol)
+ HttpBindingGenerator(protocol, codegenContext, codegenContext.symbolProvider, operationShape)
fun generateDeserializeHeaderFn(binding: HttpBindingDescriptor): RuntimeType =
httpBindingGenerator.generateDeserializeHeaderFn(binding)
@@ -34,11 +30,7 @@ class ResponseBindingGenerator(
fun generateDeserializePayloadFn(
binding: HttpBindingDescriptor,
- errorT: RuntimeType,
+ errorSymbol: Symbol,
payloadParser: RustWriter.(String) -> Unit,
- ): RuntimeType = httpBindingGenerator.generateDeserializePayloadFn(
- binding,
- errorT,
- payloadParser,
- )
+ ): RuntimeType = httpBindingGenerator.generateDeserializePayloadFn(binding, errorSymbol, payloadParser)
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt
index b8d6a652e80..31b1fb64ad6 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt
@@ -6,27 +6,29 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.protocol
import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerator
+import software.amazon.smithy.rust.codegen.client.smithy.generators.client.fluentBuilderType
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
-import software.amazon.smithy.rust.codegen.core.rustlang.docLink
+import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
+import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolTraitImplGenerator
import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
import software.amazon.smithy.rust.codegen.core.util.inputShape
open class ClientProtocolGenerator(
- codegenContext: CodegenContext,
+ private val codegenContext: ClientCodegenContext,
private val protocol: Protocol,
/**
* Operations generate a `make_operation(&config)` method to build a `aws_smithy_http::Operation` that can be dispatched
@@ -50,40 +52,77 @@ open class ClientProtocolGenerator(
customizations: List,
) {
val inputShape = operationShape.inputShape(model)
- val builderGenerator = BuilderGenerator(model, symbolProvider, operationShape.inputShape(model))
- builderGenerator.render(inputWriter)
// impl OperationInputShape { ... }
- val operationName = symbolProvider.toSymbol(operationShape).name
- inputWriter.implBlock(inputShape, symbolProvider) {
+ inputWriter.implBlock(symbolProvider.toSymbol(inputShape)) {
writeCustomizations(
customizations,
OperationSection.InputImpl(customizations, operationShape, inputShape, protocol),
)
makeOperationGenerator.generateMakeOperation(this, operationShape, customizations)
+ }
- // pub fn builder() -> ... { }
- builderGenerator.renderConvenienceMethod(this)
+ when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
+ true -> renderOperationStruct(operationWriter, operationShape, customizations)
+ else -> oldRenderOperationStruct(operationWriter, operationShape, inputShape, customizations)
}
+ }
+
+ private fun renderOperationStruct(
+ operationWriter: RustWriter,
+ operationShape: OperationShape,
+ customizations: List,
+ ) {
+ val operationName = symbolProvider.toSymbol(operationShape).name
// pub struct Operation { ... }
- val fluentBuilderName = FluentClientGenerator.clientOperationFnName(operationShape, symbolProvider)
operationWriter.rust(
+ """
+ /// `ParseStrictResponse` impl for `$operationName`.
+ """,
+ )
+ Attribute(derive(RuntimeType.Clone, RuntimeType.Default, RuntimeType.Debug)).render(operationWriter)
+ Attribute.NonExhaustive.render(operationWriter)
+ Attribute.DocHidden.render(operationWriter)
+ operationWriter.rust("pub struct $operationName;")
+ operationWriter.implBlock(symbolProvider.toSymbol(operationShape)) {
+ rustBlock("pub(crate) fn new() -> Self") {
+ rust("Self")
+ }
+
+ writeCustomizations(customizations, OperationSection.OperationImplBlock(customizations))
+ }
+ traitGenerator.generateTraitImpls(operationWriter, operationShape, customizations)
+ }
+
+ // TODO(CrateReorganization): Remove this function when removing `enableNewCrateOrganizationScheme`
+ private fun oldRenderOperationStruct(
+ operationWriter: RustWriter,
+ operationShape: OperationShape,
+ inputShape: StructureShape,
+ customizations: List,
+ ) {
+ val operationName = symbolProvider.toSymbol(operationShape).name
+
+ // pub struct Operation { ... }
+ val fluentBuilderName = FluentClientGenerator.clientOperationFnName(operationShape, symbolProvider)
+ operationWriter.rustTemplate(
"""
/// Operation shape for `$operationName`.
///
/// This is usually constructed for you using the the fluent builder returned by
- /// [`$fluentBuilderName`](${docLink("crate::client::Client::$fluentBuilderName")}).
+ /// [`$fluentBuilderName`](#{fluentBuilder}).
///
- /// See [`crate::client::fluent_builders::$operationName`] for more details about the operation.
+ /// `ParseStrictResponse` impl for `$operationName`.
""",
+ "fluentBuilder" to operationShape.fluentBuilderType(codegenContext, symbolProvider),
)
Attribute(derive(RuntimeType.Clone, RuntimeType.Default, RuntimeType.Debug)).render(operationWriter)
operationWriter.rustBlock("pub struct $operationName") {
write("_private: ()")
}
- operationWriter.implBlock(operationShape, symbolProvider) {
- builderGenerator.renderConvenienceMethod(this)
+ operationWriter.implBlock(symbolProvider.toSymbol(operationShape)) {
+ BuilderGenerator.renderConvenienceMethod(this, symbolProvider, inputShape)
rust("/// Creates a new `$operationName` operation.")
rustBlock("pub fn new() -> Self") {
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt
index 541af1ceb56..3c37049eb3a 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt
@@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.protocol
import software.amazon.smithy.aws.traits.ServiceTrait
import software.amazon.smithy.model.shapes.BlobShape
import software.amazon.smithy.model.shapes.OperationShape
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.http.RequestBindingGenerator
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
@@ -54,7 +55,7 @@ open class MakeOperationGenerator(
?: codegenContext.serviceShape.id.getName(codegenContext.serviceShape)
private val codegenScope = arrayOf(
- "config" to RuntimeType.Config,
+ "config" to ClientRustModule.Config,
"header_util" to RuntimeType.smithyHttp(runtimeConfig).resolve("header"),
"http" to RuntimeType.Http,
"HttpRequestBuilder" to RuntimeType.HttpRequestBuilder,
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt
index 476e67ef5a0..5e9fc23cb0f 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt
@@ -19,13 +19,12 @@ import software.amazon.smithy.protocoltests.traits.HttpRequestTestsTrait
import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase
import software.amazon.smithy.protocoltests.traits.HttpResponseTestsTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.allow
-import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
-import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.escape
import software.amazon.smithy.rust.codegen.core.rustlang.rust
@@ -34,7 +33,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.withBlock
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.core.util.dq
import software.amazon.smithy.rust.codegen.core.util.getTrait
@@ -91,14 +89,10 @@ class ProtocolTestGenerator(
if (allTests.isNotEmpty()) {
val operationName = operationSymbol.name
val testModuleName = "${operationName.toSnakeCase()}_request_test"
- val moduleMeta = RustMetadata(
- visibility = Visibility.PRIVATE,
- additionalAttributes = listOf(
- Attribute.CfgTest,
- Attribute(allow("unreachable_code", "unused_variables")),
- ),
+ val additionalAttributes = listOf(
+ Attribute(allow("unreachable_code", "unused_variables")),
)
- writer.withInlineModule(RustModule.LeafModule(testModuleName, moduleMeta, inline = true)) {
+ writer.withInlineModule(RustModule.inlineTests(testModuleName, additionalAttributes = additionalAttributes)) {
renderAllTestCases(allTests)
}
}
@@ -175,12 +169,12 @@ class ProtocolTestGenerator(
} ?: writable { }
rustTemplate(
"""
- let builder = #{Config}::Config::builder().with_test_defaults().endpoint_resolver("https://example.com");
+ let builder = #{config}::Config::builder().with_test_defaults().endpoint_resolver("https://example.com");
#{customParams}
let config = builder.build();
""",
- "Config" to RuntimeType.Config,
+ "config" to ClientRustModule.Config,
"customParams" to customParams,
)
writeInline("let input =")
@@ -217,9 +211,9 @@ class ProtocolTestGenerator(
checkQueryParams(this, httpRequestTestCase.queryParams)
checkForbidQueryParams(this, httpRequestTestCase.forbidQueryParams)
checkRequiredQueryParams(this, httpRequestTestCase.requireQueryParams)
- checkHeaders(this, "&http_request.headers()", httpRequestTestCase.headers)
- checkForbidHeaders(this, "&http_request.headers()", httpRequestTestCase.forbidHeaders)
- checkRequiredHeaders(this, "&http_request.headers()", httpRequestTestCase.requireHeaders)
+ checkHeaders(this, "http_request.headers()", httpRequestTestCase.headers)
+ checkForbidHeaders(this, "http_request.headers()", httpRequestTestCase.forbidHeaders)
+ checkRequiredHeaders(this, "http_request.headers()", httpRequestTestCase.requireHeaders)
if (protocolSupport.requestBodySerialization) {
// "If no request body is defined, then no assertions are made about the body of the message."
httpRequestTestCase.body.orNull()?.also { body ->
@@ -253,10 +247,10 @@ class ProtocolTestGenerator(
expectedShape: StructureShape,
) {
if (!protocolSupport.responseDeserialization || (
- !protocolSupport.errorDeserialization && expectedShape.hasTrait(
+ !protocolSupport.errorDeserialization && expectedShape.hasTrait(
ErrorTrait::class.java,
)
- )
+ )
) {
rust("/* test case disabled for this protocol (not yet supported) */")
return
@@ -296,49 +290,53 @@ class ProtocolTestGenerator(
"parse_http_response" to RuntimeType.parseHttpResponse(codegenContext.runtimeConfig),
)
if (expectedShape.hasTrait()) {
- val errorSymbol = operationShape.errorSymbol(codegenContext.symbolProvider)
+ val errorSymbol = codegenContext.symbolProvider.symbolForOperationError(operationShape)
val errorVariant = codegenContext.symbolProvider.toSymbol(expectedShape).name
rust("""let parsed = parsed.expect_err("should be error response");""")
- rustBlock("if let #TKind::$errorVariant(actual_error) = parsed.kind", errorSymbol) {
- rustTemplate("#{AssertEq}(expected_output, actual_error);", *codegenScope)
+ rustBlock("if let #T::$errorVariant(parsed) = parsed", errorSymbol) {
+ compareMembers(expectedShape)
}
rustBlock("else") {
rust("panic!(\"wrong variant: Got: {:?}. Expected: {:?}\", parsed, expected_output);")
}
} else {
rust("let parsed = parsed.unwrap();")
- outputShape.members().forEach { member ->
- val memberName = codegenContext.symbolProvider.toMemberName(member)
- if (member.isStreaming(codegenContext.model)) {
- rustTemplate(
- """
- #{AssertEq}(
- parsed.$memberName.collect().await.unwrap().into_bytes(),
- expected_output.$memberName.collect().await.unwrap().into_bytes()
- );
- """,
- *codegenScope,
- )
- } else {
- when (codegenContext.model.expectShape(member.target)) {
- is DoubleShape, is FloatShape -> {
- addUseImports(
- RuntimeType.protocolTest(codegenContext.runtimeConfig, "FloatEquals").toSymbol(),
- )
- rust(
- """
- assert!(parsed.$memberName.float_equals(&expected_output.$memberName),
- "Unexpected value for `$memberName` {:?} vs. {:?}", expected_output.$memberName, parsed.$memberName);
- """,
- )
- }
-
- else ->
- rustTemplate(
- """#{AssertEq}(parsed.$memberName, expected_output.$memberName, "Unexpected value for `$memberName`");""",
- *codegenScope,
- )
+ compareMembers(outputShape)
+ }
+ }
+
+ private fun RustWriter.compareMembers(shape: StructureShape) {
+ shape.members().forEach { member ->
+ val memberName = codegenContext.symbolProvider.toMemberName(member)
+ if (member.isStreaming(codegenContext.model)) {
+ rustTemplate(
+ """
+ #{AssertEq}(
+ parsed.$memberName.collect().await.unwrap().into_bytes(),
+ expected_output.$memberName.collect().await.unwrap().into_bytes()
+ );
+ """,
+ *codegenScope,
+ )
+ } else {
+ when (codegenContext.model.expectShape(member.target)) {
+ is DoubleShape, is FloatShape -> {
+ addUseImports(
+ RuntimeType.protocolTest(codegenContext.runtimeConfig, "FloatEquals").toSymbol(),
+ )
+ rust(
+ """
+ assert!(parsed.$memberName.float_equals(&expected_output.$memberName),
+ "Unexpected value for `$memberName` {:?} vs. {:?}", expected_output.$memberName, parsed.$memberName);
+ """,
+ )
}
+
+ else ->
+ rustTemplate(
+ """#{AssertEq}(parsed.$memberName, expected_output.$memberName, "Unexpected value for `$memberName`");""",
+ *codegenScope,
+ )
}
}
}
@@ -359,7 +357,7 @@ class ProtocolTestGenerator(
assertOk(rustWriter) {
rustWriter.write(
"#T(&body, ${
- rustWriter.escape(body).dq()
+ rustWriter.escape(body).dq()
}, #T::from(${(mediaType ?: "unknown").dq()}))",
RuntimeType.protocolTest(codegenContext.runtimeConfig, "validate_body"),
RuntimeType.protocolTest(codegenContext.runtimeConfig, "MediaType"),
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt
index ecfea77ff55..a194dc98a2e 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt
@@ -7,16 +7,19 @@ package software.amazon.smithy.rust.codegen.client.smithy.protocols
import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait
+import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait
import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait
import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait
+import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsJson
import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsJsonVersion
+import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsQueryCompatible
import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsQueryProtocol
import software.amazon.smithy.rust.codegen.core.smithy.protocols.Ec2QueryProtocol
import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
@@ -25,6 +28,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolLoader
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestJson
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestXml
+import software.amazon.smithy.rust.codegen.core.util.hasTrait
class ClientProtocolLoader(supportedProtocols: ProtocolMap) :
ProtocolLoader(supportedProtocols) {
@@ -57,12 +61,20 @@ private val CLIENT_PROTOCOL_SUPPORT = ProtocolSupport(
private class ClientAwsJsonFactory(private val version: AwsJsonVersion) :
ProtocolGeneratorFactory {
- override fun protocol(codegenContext: ClientCodegenContext): Protocol = AwsJson(codegenContext, version)
+ override fun protocol(codegenContext: ClientCodegenContext): Protocol =
+ if (compatibleWithAwsQuery(codegenContext.serviceShape, version)) {
+ AwsQueryCompatible(codegenContext, AwsJson(codegenContext, version))
+ } else {
+ AwsJson(codegenContext, version)
+ }
override fun buildProtocolGenerator(codegenContext: ClientCodegenContext): HttpBoundProtocolGenerator =
HttpBoundProtocolGenerator(codegenContext, protocol(codegenContext))
override fun support(): ProtocolSupport = CLIENT_PROTOCOL_SUPPORT
+
+ private fun compatibleWithAwsQuery(serviceShape: ServiceShape, version: AwsJsonVersion) =
+ serviceShape.hasTrait() && version == AwsJsonVersion.Json10
}
private class ClientAwsQueryFactory : ProtocolGeneratorFactory {
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt
index 0592d91cd9a..f47a15e559c 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt
@@ -9,6 +9,7 @@ import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.ErrorTrait
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.http.ResponseBindingGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.MakeOperationGenerator
@@ -29,8 +30,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustom
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.builderSymbol
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolTraitImplGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName
import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpBindingDescriptor
@@ -49,7 +48,7 @@ import software.amazon.smithy.rust.codegen.core.util.outputShape
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
class HttpBoundProtocolGenerator(
- codegenContext: CodegenContext,
+ codegenContext: ClientCodegenContext,
protocol: Protocol,
) : ClientProtocolGenerator(
codegenContext,
@@ -116,6 +115,7 @@ class HttpBoundProtocolTraitImplGenerator(
impl #{ParseStrict} for $operationName {
type Output = std::result::Result<#{O}, #{E}>;
fn parse(&self, response: {http}::Response<#{Bytes}>) -> Self::Output {
+ #{BeforeParseResponse}
if !response.status().is_success() && response.status().as_u16() != $successCode {
#{parse_error}(response)
} else {
@@ -125,9 +125,12 @@ class HttpBoundProtocolTraitImplGenerator(
}""",
*codegenScope,
"O" to outputSymbol,
- "E" to operationShape.errorSymbol(symbolProvider),
- "parse_error" to parseError(operationShape),
+ "E" to symbolProvider.symbolForOperationError(operationShape),
+ "parse_error" to parseError(operationShape, customizations),
"parse_response" to parseResponse(operationShape, customizations),
+ "BeforeParseResponse" to writable {
+ writeCustomizations(customizations, OperationSection.BeforeParseResponse(customizations, "response"))
+ },
)
}
@@ -156,18 +159,18 @@ class HttpBoundProtocolTraitImplGenerator(
}
""",
"O" to outputSymbol,
- "E" to operationShape.errorSymbol(symbolProvider),
+ "E" to symbolProvider.symbolForOperationError(operationShape),
"parse_streaming_response" to parseStreamingResponse(operationShape, customizations),
- "parse_error" to parseError(operationShape),
+ "parse_error" to parseError(operationShape, customizations),
*codegenScope,
)
}
- private fun parseError(operationShape: OperationShape): RuntimeType {
+ private fun parseError(operationShape: OperationShape, customizations: List): RuntimeType {
val fnName = "parse_${operationShape.id.name.toSnakeCase()}_error"
val outputShape = operationShape.outputShape(model)
val outputSymbol = symbolProvider.toSymbol(outputShape)
- val errorSymbol = operationShape.errorSymbol(symbolProvider)
+ val errorSymbol = symbolProvider.symbolForOperationError(operationShape)
return RuntimeType.forInlineFun(fnName, operationDeserModule) {
Attribute.AllowClippyUnnecessaryWraps.render(this)
rustBlockTemplate(
@@ -176,11 +179,17 @@ class HttpBoundProtocolTraitImplGenerator(
"O" to outputSymbol,
"E" to errorSymbol,
) {
+ Attribute.AllowUnusedMut.render(this)
rust(
- "let generic = #T(response).map_err(#T::unhandled)?;",
- protocol.parseHttpGenericError(operationShape),
+ "let mut generic_builder = #T(response).map_err(#T::unhandled)?;",
+ protocol.parseHttpErrorMetadata(operationShape),
errorSymbol,
)
+ writeCustomizations(
+ customizations,
+ OperationSection.PopulateErrorMetadataExtras(customizations, "generic_builder", "response"),
+ )
+ rust("let generic = generic_builder.build();")
if (operationShape.operationErrors(model).isNotEmpty()) {
rustTemplate(
"""
@@ -200,8 +209,8 @@ class HttpBoundProtocolTraitImplGenerator(
val variantName = symbolProvider.toSymbol(model.expectShape(error.id)).name
val errorCode = httpBindingResolver.errorCode(errorShape).dq()
withBlock(
- "$errorCode => #1T { meta: generic, kind: #1TKind::$variantName({",
- "})},",
+ "$errorCode => #1T::$variantName({",
+ "}),",
errorSymbol,
) {
Attribute.AllowUnusedMut.render(this)
@@ -212,7 +221,14 @@ class HttpBoundProtocolTraitImplGenerator(
errorShape,
httpBindingResolver.errorResponseBindings(errorShape),
errorSymbol,
- listOf(),
+ listOf(object : OperationCustomization() {
+ override fun section(section: OperationSection): Writable = writable {
+ if (section is OperationSection.MutateOutput) {
+ rust("let output = output.meta(generic);")
+ }
+ }
+ },
+ ),
)
}
}
@@ -241,7 +257,7 @@ class HttpBoundProtocolTraitImplGenerator(
val fnName = "parse_${operationShape.id.name.toSnakeCase()}"
val outputShape = operationShape.outputShape(model)
val outputSymbol = symbolProvider.toSymbol(outputShape)
- val errorSymbol = operationShape.errorSymbol(symbolProvider)
+ val errorSymbol = symbolProvider.symbolForOperationError(operationShape)
return RuntimeType.forInlineFun(fnName, operationDeserModule) {
Attribute.AllowClippyUnnecessaryWraps.render(this)
rustBlockTemplate(
@@ -270,7 +286,7 @@ class HttpBoundProtocolTraitImplGenerator(
val fnName = "parse_${operationShape.id.name.toSnakeCase()}_response"
val outputShape = operationShape.outputShape(model)
val outputSymbol = symbolProvider.toSymbol(outputShape)
- val errorSymbol = operationShape.errorSymbol(symbolProvider)
+ val errorSymbol = symbolProvider.symbolForOperationError(operationShape)
return RuntimeType.forInlineFun(fnName, operationDeserModule) {
Attribute.AllowClippyUnnecessaryWraps.render(this)
rustBlockTemplate(
@@ -296,13 +312,13 @@ class HttpBoundProtocolTraitImplGenerator(
operationShape: OperationShape,
outputShape: StructureShape,
bindings: List,
- errorSymbol: RuntimeType,
+ errorSymbol: Symbol,
customizations: List,
) {
val httpBindingGenerator = ResponseBindingGenerator(protocol, codegenContext, operationShape)
val structuredDataParser = protocol.structuredDataParser(operationShape)
Attribute.AllowUnusedMut.render(this)
- rust("let mut output = #T::default();", outputShape.builderSymbol(symbolProvider))
+ rust("let mut output = #T::default();", symbolProvider.symbolForBuilder(outputShape))
// avoid non-usage warnings for response
rust("let _ = response;")
if (outputShape.id == operationShape.output.get()) {
@@ -334,7 +350,9 @@ class HttpBoundProtocolTraitImplGenerator(
val err = if (BuilderGenerator.hasFallibleBuilder(outputShape, symbolProvider)) {
".map_err(${format(errorSymbol)}::unhandled)?"
- } else ""
+ } else {
+ ""
+ }
writeCustomizations(customizations, OperationSection.MutateOutput(customizations, operationShape))
@@ -352,7 +370,7 @@ class HttpBoundProtocolTraitImplGenerator(
httpBindingGenerator: ResponseBindingGenerator,
structuredDataParser: StructuredDataParserGenerator,
): Writable? {
- val errorSymbol = operationShape.errorSymbol(symbolProvider)
+ val errorSymbol = symbolProvider.symbolForOperationError(operationShape)
val member = binding.member
return when (binding.location) {
HttpLocation.HEADER -> writable {
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt
index 7e69ce6995a..02d61d9905e 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/AddErrorMessage.kt
@@ -19,7 +19,7 @@ import java.util.logging.Logger
*
* Not all errors are modeled with an error message field. However, in many cases, the server can still send an error.
* If an error, specifically, a structure shape with the error trait does not have a member `message` or `Message`,
- * this transformer will add a `message` member targeting a string.
+ * this transformer will add a `Message` member targeting a string.
*
* This ensures that we always generate a modeled error message field enabling end users to easily extract the error
* message when present.
@@ -37,7 +37,7 @@ object AddErrorMessage {
val addMessageField = shape.hasTrait() && shape is StructureShape && shape.errorMessageMember() == null
if (addMessageField && shape is StructureShape) {
logger.info("Adding message field to ${shape.id}")
- shape.toBuilder().addMember("message", ShapeId.from("smithy.api#String")).build()
+ shape.toBuilder().addMember("Message", ShapeId.from("smithy.api#String")).build()
} else {
shape
}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt
new file mode 100644
index 00000000000..138f43cd264
--- /dev/null
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/ClientCodegenIntegrationTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.testutil
+
+import software.amazon.smithy.build.PluginContext
+import software.amazon.smithy.build.SmithyBuildPlugin
+import software.amazon.smithy.model.Model
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin
+import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
+import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
+import software.amazon.smithy.rust.codegen.core.testutil.codegenIntegrationTest
+import java.nio.file.Path
+
+fun clientIntegrationTest(
+ model: Model,
+ params: IntegrationTestParams = IntegrationTestParams(),
+ additionalDecorators: List = listOf(),
+ test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
+): Path {
+ fun invokeRustCodegenPlugin(ctx: PluginContext) {
+ val codegenDecorator = object : ClientCodegenDecorator {
+ override val name: String = "Add tests"
+ override val order: Byte = 0
+
+ override fun classpathDiscoverable(): Boolean = false
+
+ override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
+ test(codegenContext, rustCrate)
+ }
+ }
+ RustClientCodegenPlugin().executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray())
+ }
+ return codegenIntegrationTest(model, params, invokePlugin = ::invokeRustCodegenPlugin)
+}
+
+/**
+ * A `SmithyBuildPlugin` that accepts an additional decorator.
+ *
+ * This exists to allow tests to easily customize the _real_ build without needing to list out customizations
+ * or attempt to manually discover them from the path.
+ */
+abstract class ClientDecoratableBuildPlugin : SmithyBuildPlugin {
+ abstract fun executeWithDecorator(
+ context: PluginContext,
+ vararg decorator: ClientCodegenDecorator,
+ )
+
+ override fun execute(context: PluginContext) {
+ executeWithDecorator(context)
+ }
+}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt
deleted file mode 100644
index c764e290595..00000000000
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/CodegenIntegrationTest.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package software.amazon.smithy.rust.codegen.client.testutil
-
-import software.amazon.smithy.build.PluginContext
-import software.amazon.smithy.build.SmithyBuildPlugin
-import software.amazon.smithy.model.Model
-import software.amazon.smithy.model.node.ObjectNode
-import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
-import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin
-import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
-import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
-import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext
-import software.amazon.smithy.rust.codegen.core.testutil.printGeneratedFiles
-import software.amazon.smithy.rust.codegen.core.util.runCommand
-import java.io.File
-import java.nio.file.Path
-
-/**
- * Run cargo test on a true, end-to-end, codegen product of a given model.
- *
- * For test purposes, additional codegen decorators can also be composed.
- */
-fun clientIntegrationTest(
- model: Model,
- additionalDecorators: List = listOf(),
- addModuleToEventStreamAllowList: Boolean = false,
- service: String? = null,
- runtimeConfig: RuntimeConfig? = null,
- additionalSettings: ObjectNode = ObjectNode.builder().build(),
- command: ((Path) -> Unit)? = null,
- test: (ClientCodegenContext, RustCrate) -> Unit = { _, _ -> },
-): Path {
- return codegenIntegrationTest(
- model,
- RustClientCodegenPlugin(),
- additionalDecorators,
- addModuleToEventStreamAllowList = addModuleToEventStreamAllowList,
- service = service,
- runtimeConfig = runtimeConfig,
- additionalSettings = additionalSettings,
- test = test,
- command = command,
- )
-}
-
-/**
- * A Smithy BuildPlugin that accepts an additional decorator
- *
- * This exists to allow tests to easily customize the _real_ build without needing to list out customizations
- * or attempt to manually discover them from the path
- */
-abstract class DecoratableBuildPlugin : SmithyBuildPlugin {
- abstract fun executeWithDecorator(
- context: PluginContext,
- vararg decorator: ClientCodegenDecorator,
- )
-
- override fun execute(context: PluginContext) {
- executeWithDecorator(context)
- }
-}
-
-// TODO(https://github.com/awslabs/smithy-rs/issues/1864): move to core once CodegenDecorator is in core
-private fun codegenIntegrationTest(
- model: Model,
- buildPlugin: DecoratableBuildPlugin,
- additionalDecorators: List,
- additionalSettings: ObjectNode = ObjectNode.builder().build(),
- addModuleToEventStreamAllowList: Boolean = false,
- service: String? = null,
- runtimeConfig: RuntimeConfig? = null,
- overrideTestDir: File? = null, test: (ClientCodegenContext, RustCrate) -> Unit,
- command: ((Path) -> Unit)? = null,
-): Path {
- val (ctx, testDir) = generatePluginContext(
- model,
- additionalSettings,
- addModuleToEventStreamAllowList,
- service,
- runtimeConfig,
- overrideTestDir,
- )
-
- val codegenDecorator = object : ClientCodegenDecorator {
- override val name: String = "Add tests"
- override val order: Byte = 0
-
- override fun classpathDiscoverable(): Boolean = false
-
- override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
- test(codegenContext, rustCrate)
- }
- }
- buildPlugin.executeWithDecorator(ctx, codegenDecorator, *additionalDecorators.toTypedArray())
- ctx.fileManifest.printGeneratedFiles()
- command?.invoke(testDir) ?: "cargo test".runCommand(testDir, environment = mapOf("RUSTFLAGS" to "-D warnings"))
- return testDir
-}
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestConfigCustomization.kt
index da2362a195a..cb066882254 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestConfigCustomization.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestConfigCustomization.kt
@@ -5,10 +5,10 @@
package software.amazon.smithy.rust.codegen.client.testutil
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfigGenerator
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.writable
@@ -74,7 +74,7 @@ fun validateConfigCustomizations(
fun stubConfigProject(customization: ConfigCustomization, project: TestWriterDelegator): TestWriterDelegator {
val customizations = listOf(stubConfigCustomization("a")) + customization + stubConfigCustomization("b")
val generator = ServiceConfigGenerator(customizations = customizations.toList())
- project.withModule(RustModule.Config) {
+ project.withModule(ClientRustModule.Config) {
generator.render(this)
unitTest(
"config_send_sync",
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestHelpers.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestHelpers.kt
index 1b3f8139594..e3fa9faa796 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestHelpers.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/testutil/TestHelpers.kt
@@ -6,22 +6,24 @@
package software.amazon.smithy.rust.codegen.client.testutil
import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.knowledge.NullableIndex
import software.amazon.smithy.model.node.ObjectNode
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenConfig
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustSettings
+import software.amazon.smithy.rust.codegen.client.smithy.OldModuleSchemeClientModuleProvider
import software.amazon.smithy.rust.codegen.client.smithy.RustClientCodegenPlugin
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
-import software.amazon.smithy.rust.codegen.core.smithy.CoreRustSettings
+import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
+import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
+import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
-import software.amazon.smithy.rust.codegen.core.testutil.TestSymbolVisitorConfig
-import software.amazon.smithy.rust.codegen.core.testutil.testRustSettings
+import software.amazon.smithy.rust.codegen.core.testutil.TestWriterDelegator
-fun clientTestRustSettings(
+fun testClientRustSettings(
service: ShapeId = ShapeId.from("notrelevant#notrelevant"),
moduleName: String = "test-module",
moduleVersion: String = "0.0.1",
@@ -47,25 +49,41 @@ fun clientTestRustSettings(
customizationConfig,
)
+val TestClientRustSymbolProviderConfig = RustSymbolProviderConfig(
+ runtimeConfig = TestRuntimeConfig,
+ renameExceptions = true,
+ nullabilityCheckMode = NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1,
+ moduleProvider = OldModuleSchemeClientModuleProvider,
+)
+
fun testSymbolProvider(model: Model, serviceShape: ServiceShape? = null): RustSymbolProvider =
RustClientCodegenPlugin.baseSymbolProvider(
+ testClientRustSettings(),
model,
serviceShape ?: ServiceShape.builder().version("test").id("test#Service").build(),
- TestSymbolVisitorConfig,
+ TestClientRustSymbolProviderConfig,
)
-fun testCodegenContext(
+fun testClientCodegenContext(
model: Model,
+ symbolProvider: RustSymbolProvider? = null,
serviceShape: ServiceShape? = null,
- settings: CoreRustSettings = testRustSettings(),
- codegenTarget: CodegenTarget = CodegenTarget.CLIENT,
-): CodegenContext = CodegenContext(
+ settings: ClientRustSettings = testClientRustSettings(),
+ rootDecorator: ClientCodegenDecorator? = null,
+): ClientCodegenContext = ClientCodegenContext(
model,
- testSymbolProvider(model),
+ symbolProvider ?: testSymbolProvider(model),
serviceShape
?: model.serviceShapes.firstOrNull()
?: ServiceShape.builder().version("test").id("test#Service").build(),
ShapeId.from("test#Protocol"),
settings,
- codegenTarget,
+ rootDecorator ?: CombinedClientCodegenDecorator(emptyList()),
)
+
+fun TestWriterDelegator.clientRustSettings() =
+ testClientRustSettings(
+ service = ShapeId.from("fake#Fake"),
+ moduleName = "test_${baseDir.toFile().nameWithoutExtension}",
+ codegenConfig = codegenConfig as ClientCodegenConfig,
+ )
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ApiKeyAuthDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ApiKeyAuthDecoratorTest.kt
new file mode 100644
index 00000000000..41e3ebae7aa
--- /dev/null
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ApiKeyAuthDecoratorTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.customizations
+
+import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
+import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
+import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
+import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings
+
+internal class ApiKeyAuthDecoratorTest {
+ private val modelQuery = """
+ namespace test
+
+ use aws.api#service
+ use aws.protocols#restJson1
+
+ @service(sdkId: "Test Api Key Auth")
+ @restJson1
+ @httpApiKeyAuth(name: "api_key", in: "query")
+ @auth([httpApiKeyAuth])
+ service TestService {
+ version: "2023-01-01",
+ operations: [SomeOperation]
+ }
+
+ structure SomeOutput {
+ someAttribute: Long,
+ someVal: String
+ }
+
+ @http(uri: "/SomeOperation", method: "GET")
+ operation SomeOperation {
+ output: SomeOutput
+ }
+ """.asSmithyModel()
+
+ @Test
+ fun `set an api key in query parameter`() {
+ val testDir = clientIntegrationTest(
+ modelQuery,
+ // just run integration tests
+ IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }),
+ ) { clientCodegenContext, rustCrate ->
+ rustCrate.integrationTest("api_key_present_in_property_bag") {
+ val moduleName = clientCodegenContext.moduleUseName()
+ Attribute.TokioTest.render(this)
+ rust(
+ """
+ async fn api_key_present_in_property_bag() {
+ use aws_smithy_http_auth::api_key::AuthApiKey;
+ let api_key_value = "some-api-key";
+ let conf = $moduleName::Config::builder()
+ .api_key(AuthApiKey::new(api_key_value))
+ .build();
+ let operation = $moduleName::input::SomeOperationInput::builder()
+ .build()
+ .expect("input is valid")
+ .make_operation(&conf)
+ .await
+ .expect("valid operation");
+ let props = operation.properties();
+ let api_key_config = props.get::().expect("api key in the bag");
+ assert_eq!(
+ api_key_config,
+ &AuthApiKey::new(api_key_value),
+ );
+ }
+ """,
+ )
+ }
+
+ rustCrate.integrationTest("api_key_auth_is_set_in_query") {
+ val moduleName = clientCodegenContext.moduleUseName()
+ Attribute.TokioTest.render(this)
+ rust(
+ """
+ async fn api_key_auth_is_set_in_query() {
+ use aws_smithy_http_auth::api_key::AuthApiKey;
+ let api_key_value = "some-api-key";
+ let conf = $moduleName::Config::builder()
+ .api_key(AuthApiKey::new(api_key_value))
+ .build();
+ let operation = $moduleName::input::SomeOperationInput::builder()
+ .build()
+ .expect("input is valid")
+ .make_operation(&conf)
+ .await
+ .expect("valid operation");
+ assert_eq!(
+ operation.request().uri().query(),
+ Some("api_key=some-api-key"),
+ );
+ }
+ """,
+ )
+ }
+ }
+ "cargo clippy".runWithWarnings(testDir)
+ }
+
+ private val modelHeader = """
+ namespace test
+
+ use aws.api#service
+ use aws.protocols#restJson1
+
+ @service(sdkId: "Test Api Key Auth")
+ @restJson1
+ @httpApiKeyAuth(name: "authorization", in: "header", scheme: "ApiKey")
+ @auth([httpApiKeyAuth])
+ service TestService {
+ version: "2023-01-01",
+ operations: [SomeOperation]
+ }
+
+ structure SomeOutput {
+ someAttribute: Long,
+ someVal: String
+ }
+
+ @http(uri: "/SomeOperation", method: "GET")
+ operation SomeOperation {
+ output: SomeOutput
+ }
+ """.asSmithyModel()
+
+ @Test
+ fun `set an api key in http header`() {
+ val testDir = clientIntegrationTest(
+ modelHeader,
+ // just run integration tests
+ IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }),
+ ) { clientCodegenContext, rustCrate ->
+ rustCrate.integrationTest("api_key_auth_is_set_in_http_header") {
+ val moduleName = clientCodegenContext.moduleUseName()
+ Attribute.TokioTest.render(this)
+ rust(
+ """
+ async fn api_key_auth_is_set_in_http_header() {
+ use aws_smithy_http_auth::api_key::AuthApiKey;
+ let api_key_value = "some-api-key";
+ let conf = $moduleName::Config::builder()
+ .api_key(AuthApiKey::new(api_key_value))
+ .build();
+ let operation = $moduleName::input::SomeOperationInput::builder()
+ .build()
+ .expect("input is valid")
+ .make_operation(&conf)
+ .await
+ .expect("valid operation");
+ let props = operation.properties();
+ let api_key_config = props.get::().expect("api key in the bag");
+ assert_eq!(
+ api_key_config,
+ &AuthApiKey::new(api_key_value),
+ );
+ assert_eq!(
+ operation.request().headers().contains_key("authorization"),
+ true,
+ );
+ }
+ """,
+ )
+ }
+ }
+ "cargo clippy".runWithWarnings(testDir)
+ }
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt
index 0c57ba622a7..eb166ae1c69 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt
@@ -19,6 +19,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
@@ -61,7 +62,7 @@ internal class HttpVersionListGeneratorTest {
"""
async fn test_http_version_list_defaults() {
let conf = $moduleName::Config::builder().build();
- let op = $moduleName::operation::SayHello::builder()
+ let op = $moduleName::input::SayHelloInput::builder()
.greeting("hello")
.build().expect("valid operation")
.make_operation(&conf).await.expect("hello is a valid prefix");
@@ -112,7 +113,7 @@ internal class HttpVersionListGeneratorTest {
"""
async fn test_http_version_list_defaults() {
let conf = $moduleName::Config::builder().build();
- let op = $moduleName::operation::SayHello::builder()
+ let op = $moduleName::input::SayHelloInput::builder()
.greeting("hello")
.build().expect("valid operation")
.make_operation(&conf).await.expect("hello is a valid prefix");
@@ -170,8 +171,8 @@ internal class HttpVersionListGeneratorTest {
clientIntegrationTest(
model,
- listOf(FakeSigningDecorator()),
- addModuleToEventStreamAllowList = true,
+ IntegrationTestParams(addModuleToEventStreamAllowList = true),
+ additionalDecorators = listOf(FakeSigningDecorator()),
) { clientCodegenContext, rustCrate ->
val moduleName = clientCodegenContext.moduleUseName()
rustCrate.integrationTest("validate_eventstream_http") {
@@ -180,7 +181,7 @@ internal class HttpVersionListGeneratorTest {
"""
async fn test_http_version_list_defaults() {
let conf = $moduleName::Config::builder().build();
- let op = $moduleName::operation::SayHello::builder()
+ let op = $moduleName::input::SayHelloInput::builder()
.build().expect("valid operation")
.make_operation(&conf).await.unwrap();
let properties = op.properties();
@@ -204,7 +205,9 @@ class FakeSigningDecorator : ClientCodegenDecorator {
codegenContext: ClientCodegenContext,
baseCustomizations: List,
): List {
- return baseCustomizations.filterNot { it is EventStreamSigningConfig } + FakeSigningConfig(codegenContext.runtimeConfig)
+ return baseCustomizations.filterNot {
+ it is EventStreamSigningConfig
+ } + FakeSigningConfig(codegenContext.runtimeConfig)
}
}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ResiliencyConfigCustomizationTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ResiliencyConfigCustomizationTest.kt
index 2d2cf76916d..612bf8e3335 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ResiliencyConfigCustomizationTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/ResiliencyConfigCustomizationTest.kt
@@ -6,16 +6,17 @@
package software.amazon.smithy.rust.codegen.client.customizations
import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenConfig
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyConfigCustomization
import software.amazon.smithy.rust.codegen.client.smithy.customizations.ResiliencyReExportCustomization
+import software.amazon.smithy.rust.codegen.client.testutil.clientRustSettings
import software.amazon.smithy.rust.codegen.client.testutil.stubConfigProject
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
-import software.amazon.smithy.rust.codegen.core.testutil.rustSettings
internal class ResiliencyConfigCustomizationTest {
private val baseModel = """
@@ -36,9 +37,9 @@ internal class ResiliencyConfigCustomizationTest {
@Test
fun `generates a valid config`() {
- val model = RecursiveShapeBoxer.transform(OperationNormalizer.transform(baseModel))
- val project = TestWorkspace.testProject()
- val codegenContext = testCodegenContext(model, settings = project.rustSettings())
+ val model = RecursiveShapeBoxer().transform(OperationNormalizer.transform(baseModel))
+ val project = TestWorkspace.testProject(model, ClientCodegenConfig())
+ val codegenContext = testClientCodegenContext(model, settings = project.clientRustSettings())
stubConfigProject(ResiliencyConfigCustomization(codegenContext), project)
ResiliencyReExportCustomization(codegenContext.runtimeConfig).extras(project)
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextParamsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextConfigCustomizationTest.kt
similarity index 94%
rename from codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextParamsDecoratorTest.kt
rename to codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextConfigCustomizationTest.kt
index cb964902097..8c3a4122e94 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextParamsDecoratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/ClientContextConfigCustomizationTest.kt
@@ -7,14 +7,14 @@ package software.amazon.smithy.rust.codegen.client.endpoint
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.ClientContextConfigCustomization
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.client.testutil.validateConfigCustomizations
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
-class ClientContextParamsDecoratorTest {
+class ClientContextConfigCustomizationTest {
val model = """
namespace test
use smithy.rules#clientContextParams
@@ -52,6 +52,6 @@ class ClientContextParamsDecoratorTest {
""",
)
}
- validateConfigCustomizations(ClientContextConfigCustomization(testCodegenContext(model)), project)
+ validateConfigCustomizations(ClientContextConfigCustomization(testClientCodegenContext(model)), project)
}
}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt
index ccd028ebcb8..179cbbc944a 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointResolverGeneratorTest.kt
@@ -23,7 +23,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.End
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointTestGenerator
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.SmithyEndpointsStdLib
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.awsStandardLib
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
@@ -64,7 +64,7 @@ class EndpointResolverGeneratorTest {
paramsType = EndpointParamsGenerator(suite.ruleSet().parameters).paramsStruct(),
resolverType = ruleset,
suite.ruleSet().parameters,
- codegenContext = testCodegenContext(model = Model.builder().build()),
+ codegenContext = testClientCodegenContext(model = Model.builder().build()),
endpointCustomizations = listOf(),
)
testGenerator.generate()(this)
@@ -90,7 +90,7 @@ class EndpointResolverGeneratorTest {
paramsType = EndpointParamsGenerator(suite.ruleSet().parameters).paramsStruct(),
resolverType = ruleset,
suite.ruleSet().parameters,
- codegenContext = testCodegenContext(Model.builder().build()),
+ codegenContext = testClientCodegenContext(Model.builder().build()),
endpointCustomizations = listOf(),
)
testGenerator.generate()(this)
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt
index d05f3b21de9..2b9e75f4039 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt
@@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings
@@ -123,8 +124,8 @@ class EndpointsDecoratorTest {
fun `set an endpoint in the property bag`() {
val testDir = clientIntegrationTest(
model,
- // just run integration tests
- command = { "cargo test --test *".runWithWarnings(it) },
+ // Just run integration tests.
+ IntegrationTestParams(command = { "cargo test --test *".runWithWarnings(it) }),
) { clientCodegenContext, rustCrate ->
rustCrate.integrationTest("endpoint_params_test") {
val moduleName = clientCodegenContext.moduleUseName()
@@ -133,7 +134,7 @@ class EndpointsDecoratorTest {
"""
async fn endpoint_params_are_set() {
let conf = $moduleName::Config::builder().a_string_param("hello").a_bool_param(false).build();
- let operation = $moduleName::operation::TestOperation::builder()
+ let operation = $moduleName::input::TestOperationInput::builder()
.bucket("bucket-name").build().expect("input is valid")
.make_operation(&conf).await.expect("valid operation");
use $moduleName::endpoint::{Params};
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/EventStreamSymbolProviderTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/EventStreamSymbolProviderTest.kt
index 5fb38e58e31..42663f27563 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/EventStreamSymbolProviderTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/EventStreamSymbolProviderTest.kt
@@ -10,6 +10,8 @@ import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.ShapeId
+import software.amazon.smithy.rust.codegen.client.testutil.TestClientRustSymbolProviderConfig
+import software.amazon.smithy.rust.codegen.client.testutil.testClientRustSettings
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.EventStreamSymbolProvider
@@ -17,7 +19,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitor
import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
-import software.amazon.smithy.rust.codegen.core.testutil.TestSymbolVisitorConfig
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
class EventStreamSymbolProviderTest {
@@ -46,7 +47,11 @@ class EventStreamSymbolProviderTest {
)
val service = model.expectShape(ShapeId.from("test#TestService")) as ServiceShape
- val provider = EventStreamSymbolProvider(TestRuntimeConfig, SymbolVisitor(model, service, TestSymbolVisitorConfig), model, CodegenTarget.CLIENT)
+ val provider = EventStreamSymbolProvider(
+ TestRuntimeConfig,
+ SymbolVisitor(testClientRustSettings(), model, service, TestClientRustSymbolProviderConfig),
+ CodegenTarget.CLIENT,
+ )
// Look up the synthetic input/output rather than the original input/output
val inputStream = model.expectShape(ShapeId.from("test.synthetic#TestOperationInput\$inputStream")) as MemberShape
@@ -82,7 +87,11 @@ class EventStreamSymbolProviderTest {
)
val service = model.expectShape(ShapeId.from("test#TestService")) as ServiceShape
- val provider = EventStreamSymbolProvider(TestRuntimeConfig, SymbolVisitor(model, service, TestSymbolVisitorConfig), model, CodegenTarget.CLIENT)
+ val provider = EventStreamSymbolProvider(
+ TestRuntimeConfig,
+ SymbolVisitor(testClientRustSettings(), model, service, TestClientRustSymbolProviderConfig),
+ CodegenTarget.CLIENT,
+ )
// Look up the synthetic input/output rather than the original input/output
val inputStream = model.expectShape(ShapeId.from("test.synthetic#TestOperationInput\$inputStream")) as MemberShape
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/StreamingShapeSymbolProviderTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/StreamingShapeSymbolProviderTest.kt
index 6c0c3cdadf1..a2e233c7195 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/StreamingShapeSymbolProviderTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/StreamingShapeSymbolProviderTest.kt
@@ -9,8 +9,10 @@ import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
+import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.smithy.Default
import software.amazon.smithy.rust.codegen.core.smithy.defaultValue
+import software.amazon.smithy.rust.codegen.core.smithy.rustType
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.util.lookup
@@ -38,8 +40,18 @@ internal class StreamingShapeSymbolProviderTest {
// "doing the right thing"
val modelWithOperationTraits = OperationNormalizer.transform(model)
val symbolProvider = testSymbolProvider(modelWithOperationTraits)
- symbolProvider.toSymbol(modelWithOperationTraits.lookup("test.synthetic#GenerateSpeechOutput\$data")).name shouldBe ("ByteStream")
- symbolProvider.toSymbol(modelWithOperationTraits.lookup("test.synthetic#GenerateSpeechInput\$data")).name shouldBe ("ByteStream")
+ modelWithOperationTraits.lookup("test.synthetic#GenerateSpeechOutput\$data").also { shape ->
+ symbolProvider.toSymbol(shape).also { symbol ->
+ symbol.name shouldBe "data"
+ symbol.rustType() shouldBe RustType.Opaque("ByteStream", "aws_smithy_http::byte_stream")
+ }
+ }
+ modelWithOperationTraits.lookup("test.synthetic#GenerateSpeechInput\$data").also { shape ->
+ symbolProvider.toSymbol(shape).also { symbol ->
+ symbol.name shouldBe "data"
+ symbol.rustType() shouldBe RustType.Opaque("ByteStream", "aws_smithy_http::byte_stream")
+ }
+ }
}
@Test
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGeneratorTest.kt
new file mode 100644
index 00000000000..14ed8ad37f7
--- /dev/null
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGeneratorTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators
+
+import org.junit.jupiter.api.Test
+import software.amazon.smithy.model.Model
+import software.amazon.smithy.model.shapes.StringShape
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
+import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
+import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
+import software.amazon.smithy.rust.codegen.core.testutil.unitTest
+import software.amazon.smithy.rust.codegen.core.util.lookup
+
+class ClientEnumGeneratorTest {
+ @Test
+ fun `matching on enum should be forward-compatible`() {
+ fun expectMatchExpressionCompiles(model: Model, shapeId: String, enumToMatchOn: String) {
+ val shape = model.lookup(shapeId)
+ val context = testClientCodegenContext(model)
+ val project = TestWorkspace.testProject(context.symbolProvider)
+ project.moduleFor(shape) {
+ ClientEnumGenerator(context, shape).render(this)
+ unitTest(
+ "matching_on_enum_should_be_forward_compatible",
+ """
+ match $enumToMatchOn {
+ SomeEnum::Variant1 => assert!(false, "expected `Variant3` but got `Variant1`"),
+ SomeEnum::Variant2 => assert!(false, "expected `Variant3` but got `Variant2`"),
+ other @ _ if other.as_str() == "Variant3" => assert!(true),
+ _ => assert!(false, "expected `Variant3` but got `_`"),
+ }
+ """.trimIndent(),
+ )
+ }
+ project.compileAndTest()
+ }
+
+ val modelV1 = """
+ namespace test
+
+ @enum([
+ { name: "Variant1", value: "Variant1" },
+ { name: "Variant2", value: "Variant2" },
+ ])
+ string SomeEnum
+ """.asSmithyModel()
+ val variant3AsUnknown = """SomeEnum::from("Variant3")"""
+ expectMatchExpressionCompiles(modelV1, "test#SomeEnum", variant3AsUnknown)
+
+ val modelV2 = """
+ namespace test
+
+ @enum([
+ { name: "Variant1", value: "Variant1" },
+ { name: "Variant2", value: "Variant2" },
+ { name: "Variant3", value: "Variant3" },
+ ])
+ string SomeEnum
+ """.asSmithyModel()
+ val variant3AsVariant3 = "SomeEnum::Variant3"
+ expectMatchExpressionCompiles(modelV2, "test#SomeEnum", variant3AsVariant3)
+ }
+
+ @Test
+ fun `impl debug for non-sensitive enum should implement the derived debug trait`() {
+ val model = """
+ namespace test
+ @enum([
+ { name: "Foo", value: "Foo" },
+ { name: "Bar", value: "Bar" },
+ ])
+ string SomeEnum
+ """.asSmithyModel()
+
+ val shape = model.lookup("test#SomeEnum")
+ val context = testClientCodegenContext(model)
+ val project = TestWorkspace.testProject(context.symbolProvider)
+ project.moduleFor(shape) {
+ ClientEnumGenerator(context, shape).render(this)
+ unitTest(
+ "impl_debug_for_non_sensitive_enum_should_implement_the_derived_debug_trait",
+ """
+ assert_eq!(format!("{:?}", SomeEnum::Foo), "Foo");
+ assert_eq!(format!("{:?}", SomeEnum::Bar), "Bar");
+ assert_eq!(
+ format!("{:?}", SomeEnum::from("Baz")),
+ "Unknown(UnknownVariantValue(\"Baz\"))"
+ );
+ """,
+ )
+ }
+ project.compileAndTest()
+ }
+
+ @Test
+ fun `it escapes the Unknown variant if the enum has an unknown value in the model`() {
+ val model = """
+ namespace test
+ @enum([
+ { name: "Known", value: "Known" },
+ { name: "Unknown", value: "Unknown" },
+ { name: "UnknownValue", value: "UnknownValue" },
+ ])
+ string SomeEnum
+ """.asSmithyModel()
+
+ val shape = model.lookup("test#SomeEnum")
+ val context = testClientCodegenContext(model)
+ val project = TestWorkspace.testProject(context.symbolProvider)
+ project.moduleFor(shape) {
+ ClientEnumGenerator(context, shape).render(this)
+ unitTest(
+ "it_escapes_the_unknown_variant_if_the_enum_has_an_unknown_value_in_the_model",
+ """
+ assert_eq!(SomeEnum::from("Unknown"), SomeEnum::UnknownValue);
+ assert_eq!(SomeEnum::from("UnknownValue"), SomeEnum::UnknownValue_);
+ assert_eq!(SomeEnum::from("SomethingNew"), SomeEnum::Unknown(crate::types::UnknownVariantValue("SomethingNew".to_owned())));
+ """.trimIndent(),
+ )
+ }
+ project.compileAndTest()
+ }
+
+ @Test
+ fun `generated named enums can roundtrip between string and enum value on the unknown variant`() {
+ val model = """
+ namespace test
+ @enum([
+ { value: "t2.nano", name: "T2_NANO" },
+ { value: "t2.micro", name: "T2_MICRO" },
+ ])
+ string InstanceType
+ """.asSmithyModel()
+
+ val shape = model.lookup("test#InstanceType")
+ val context = testClientCodegenContext(model)
+ val project = TestWorkspace.testProject(context.symbolProvider)
+ project.moduleFor(shape) {
+ rust("##![allow(deprecated)]")
+ ClientEnumGenerator(context, shape).render(this)
+ unitTest(
+ "generated_named_enums_roundtrip",
+ """
+ let instance = InstanceType::T2Micro;
+ assert_eq!(instance.as_str(), "t2.micro");
+ assert_eq!(InstanceType::from("t2.nano"), InstanceType::T2Nano);
+ // round trip unknown variants:
+ assert_eq!(InstanceType::from("other"), InstanceType::Unknown(crate::types::UnknownVariantValue("other".to_owned())));
+ assert_eq!(InstanceType::from("other").as_str(), "other");
+ """,
+ )
+ }
+ project.compileAndTest()
+ }
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiatorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiatorTest.kt
index f507ba2d4c7..abd497b49b8 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiatorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiatorTest.kt
@@ -8,17 +8,14 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators
import org.junit.jupiter.api.Test
import software.amazon.smithy.model.node.Node
import software.amazon.smithy.model.shapes.StringShape
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.withBlock
-import software.amazon.smithy.rust.codegen.core.smithy.generators.EnumGenerator
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
import software.amazon.smithy.rust.codegen.core.util.dq
-import software.amazon.smithy.rust.codegen.core.util.expectTrait
import software.amazon.smithy.rust.codegen.core.util.lookup
internal class ClientInstantiatorTest {
@@ -44,7 +41,7 @@ internal class ClientInstantiatorTest {
string NamedEnum
""".asSmithyModel()
- private val codegenContext = testCodegenContext(model)
+ private val codegenContext = testClientCodegenContext(model)
private val symbolProvider = codegenContext.symbolProvider
@Test
@@ -53,9 +50,9 @@ internal class ClientInstantiatorTest {
val sut = clientInstantiator(codegenContext)
val data = Node.parse("t2.nano".dq())
- val project = TestWorkspace.testProject()
- project.withModule(RustModule.Model) {
- EnumGenerator(model, symbolProvider, this, shape, shape.expectTrait()).render()
+ val project = TestWorkspace.testProject(symbolProvider)
+ project.moduleFor(shape) {
+ ClientEnumGenerator(codegenContext, shape).render(this)
unitTest("generate_named_enums") {
withBlock("let result = ", ";") {
sut.render(this, shape, data)
@@ -72,9 +69,9 @@ internal class ClientInstantiatorTest {
val sut = clientInstantiator(codegenContext)
val data = Node.parse("t2.nano".dq())
- val project = TestWorkspace.testProject()
- project.withModule(RustModule.Model) {
- EnumGenerator(model, symbolProvider, this, shape, shape.expectTrait()).render()
+ val project = TestWorkspace.testProject(symbolProvider)
+ project.moduleFor(shape) {
+ ClientEnumGenerator(codegenContext, shape).render(this)
unitTest("generate_unnamed_enums") {
withBlock("let result = ", ";") {
sut.render(this, shape, data)
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt
index 2a9787f69d4..2e3f6d6a2d7 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt
@@ -13,10 +13,10 @@ import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
+import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock
import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError
import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
@@ -50,10 +50,10 @@ internal class EndpointTraitBindingsTest {
}
""".asSmithyModel()
val operationShape: OperationShape = model.lookup("test#GetStatus")
- val sym = testSymbolProvider(model)
+ val symbolProvider = testSymbolProvider(model)
val endpointBindingGenerator = EndpointTraitBindings(
model,
- sym,
+ symbolProvider,
TestRuntimeConfig,
operationShape,
operationShape.expectTrait(EndpointTrait::class.java),
@@ -67,7 +67,7 @@ internal class EndpointTraitBindingsTest {
}
""",
)
- implBlock(model.lookup("test#GetStatusInput"), sym) {
+ implBlock(symbolProvider.toSymbol(model.lookup("test#GetStatusInput"))) {
rustBlock(
"fn endpoint_prefix(&self) -> std::result::Result<#T::endpoint::EndpointPrefix, #T>",
RuntimeType.smithyHttp(TestRuntimeConfig),
@@ -145,10 +145,10 @@ internal class EndpointTraitBindingsTest {
"""
async fn test_endpoint_prefix() {
let conf = $moduleName::Config::builder().build();
- $moduleName::operation::SayHello::builder()
+ $moduleName::input::SayHelloInput::builder()
.greeting("hey there!").build().expect("input is valid")
.make_operation(&conf).await.expect_err("no spaces or exclamation points in ep prefixes");
- let op = $moduleName::operation::SayHello::builder()
+ let op = $moduleName::input::SayHelloInput::builder()
.greeting("hello")
.build().expect("valid operation")
.make_operation(&conf).await.expect("hello is a valid prefix");
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGeneratorTest.kt
index 366ff370dc0..2aaa3e21dd8 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/config/ServiceConfigGeneratorTest.kt
@@ -8,8 +8,8 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.config
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.ServiceShape
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.writable
@@ -94,7 +94,9 @@ internal class ServiceConfigGeneratorTest {
}
ServiceConfig.BuilderStruct -> writable { rust("config_field: Option") }
ServiceConfig.BuilderImpl -> emptySection
- ServiceConfig.BuilderBuild -> writable { rust("config_field: self.config_field.unwrap_or_default(),") }
+ ServiceConfig.BuilderBuild -> writable {
+ rust("config_field: self.config_field.unwrap_or_default(),")
+ }
else -> emptySection
}
}
@@ -102,7 +104,7 @@ internal class ServiceConfigGeneratorTest {
val sut = ServiceConfigGenerator(listOf(ServiceCustomizer()))
val symbolProvider = testSymbolProvider("namespace empty".asSmithyModel())
val project = TestWorkspace.testProject(symbolProvider)
- project.withModule(RustModule.Config) {
+ project.withModule(ClientRustModule.Config) {
sut.render(this)
unitTest(
"set_config_fields",
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGeneratorTest.kt
new file mode 100644
index 00000000000..cca1dcb3d5d
--- /dev/null
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGeneratorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+
+import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
+
+class ErrorGeneratorTest {
+ val model =
+ """
+ namespace com.test
+ use aws.protocols#awsJson1_1
+
+ @awsJson1_1
+ service TestService {
+ operations: [TestOp]
+ }
+
+ operation TestOp {
+ errors: [MyError]
+ }
+
+ @error("server")
+ @retryable
+ structure MyError {
+ message: String
+ }
+ """.asSmithyModel()
+
+ @Test
+ fun `generate error structure and builder`() {
+ clientIntegrationTest(model) { _, rustCrate ->
+ rustCrate.withFile("src/error.rs") {
+ rust(
+ """
+ ##[test]
+ fn test_error_generator() {
+ use aws_smithy_types::error::metadata::{ErrorMetadata, ProvideErrorMetadata};
+ use aws_smithy_types::retry::ErrorKind;
+
+ let err = MyError::builder()
+ .meta(ErrorMetadata::builder().code("test").message("testmsg").build())
+ .message("testmsg")
+ .build();
+ assert_eq!(err.retryable_error_kind(), ErrorKind::ServerError);
+ assert_eq!("test", err.meta().code().unwrap());
+ assert_eq!("testmsg", err.meta().message().unwrap());
+ assert_eq!("testmsg", err.message().unwrap());
+ }
+ """,
+ )
+ }
+ }
+ }
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGeneratorTest.kt
new file mode 100644
index 00000000000..b81de4010c0
--- /dev/null
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/OperationErrorGeneratorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+
+import org.junit.jupiter.api.Test
+import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
+import software.amazon.smithy.rust.codegen.core.testutil.unitTest
+import software.amazon.smithy.rust.codegen.core.util.lookup
+
+class OperationErrorGeneratorTest {
+ private val model = """
+ namespace error
+
+ @aws.protocols#awsJson1_0
+ service TestService {
+ operations: [Greeting],
+ }
+
+ operation Greeting {
+ errors: [InvalidGreeting, ComplexError, FooException, Deprecated]
+ }
+
+ @error("client")
+ @retryable
+ structure InvalidGreeting {
+ message: String,
+ }
+
+ @error("server")
+ structure FooException { }
+
+ @error("server")
+ structure ComplexError {
+ abc: String,
+ other: Integer
+ }
+
+ @error("server")
+ @deprecated
+ structure Deprecated { }
+ """.asSmithyModel()
+
+ @Test
+ fun `generates combined error enums`() {
+ clientIntegrationTest(model) { _, rustCrate ->
+ rustCrate.moduleFor(model.lookup("error#FooException")) {
+ unitTest(
+ name = "generates_combined_error_enums",
+ test = """
+ let error = GreetingError::InvalidGreeting(
+ InvalidGreeting::builder()
+ .message("an error")
+ .meta(aws_smithy_types::Error::builder().code("InvalidGreeting").message("an error").build())
+ .build()
+ );
+ assert_eq!(format!("{}", error), "InvalidGreeting: an error");
+ assert_eq!(error.meta().message(), Some("an error"));
+ assert_eq!(error.meta().code(), Some("InvalidGreeting"));
+ use aws_smithy_types::retry::ProvideErrorKind;
+ assert_eq!(error.retryable_error_kind(), Some(aws_smithy_types::retry::ErrorKind::ClientError));
+
+ // Generate is_xyz methods for errors.
+ assert_eq!(error.is_invalid_greeting(), true);
+ assert_eq!(error.is_complex_error(), false);
+
+ // Unhandled variants properly delegate message.
+ let error = GreetingError::generic(aws_smithy_types::Error::builder().message("hello").build());
+ assert_eq!(error.meta().message(), Some("hello"));
+
+ let error = GreetingError::unhandled("some other error");
+ assert_eq!(error.meta().message(), None);
+ assert_eq!(error.meta().code(), None);
+
+ // Indicate the original name in the display output.
+ let error = FooError::builder().build();
+ assert_eq!(format!("{}", error), "FooError [FooException]");
+
+ let error = Deprecated::builder().build();
+ assert_eq!(error.to_string(), "Deprecated");
+ """,
+ )
+ }
+ }
+ }
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGeneratorTest.kt
new file mode 100644
index 00000000000..1cbb274cfb6
--- /dev/null
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ServiceErrorGeneratorTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.generators.error
+
+import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
+import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
+
+internal class ServiceErrorGeneratorTest {
+ @Test
+ fun `top level errors are send + sync`() {
+ val model = """
+ namespace com.example
+
+ use aws.protocols#restJson1
+
+ @restJson1
+ service HelloService {
+ operations: [SayHello],
+ version: "1"
+ }
+
+ @http(uri: "/", method: "POST")
+ operation SayHello {
+ input: EmptyStruct,
+ output: EmptyStruct,
+ errors: [SorryBusy, CanYouRepeatThat, MeDeprecated]
+ }
+
+ structure EmptyStruct { }
+
+ @error("server")
+ structure SorryBusy { }
+
+ @error("client")
+ structure CanYouRepeatThat { }
+
+ @error("client")
+ @deprecated
+ structure MeDeprecated { }
+ """.asSmithyModel()
+
+ clientIntegrationTest(model) { codegenContext, rustCrate ->
+ rustCrate.integrationTest("validate_errors") {
+ rust(
+ """
+ fn check_send_sync() {}
+
+ ##[test]
+ fn service_errors_are_send_sync() {
+ check_send_sync::<${codegenContext.moduleUseName()}::Error>()
+ }
+ """,
+ )
+ }
+ }
+ }
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGeneratorTest.kt
index 6f214e35402..be42a9647c5 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGeneratorTest.kt
@@ -11,13 +11,13 @@ import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.HttpTrait
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
-import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestJson
import software.amazon.smithy.rust.codegen.core.smithy.transformers.OperationNormalizer
@@ -127,45 +127,47 @@ class RequestBindingGeneratorTest {
private val operationShape = model.expectShape(ShapeId.from("smithy.example#PutObject"), OperationShape::class.java)
private val inputShape = model.expectShape(operationShape.input.get(), StructureShape::class.java)
- private fun renderOperation(writer: RustWriter) {
- inputShape.renderWithModelBuilder(model, symbolProvider, writer)
- val codegenContext = testCodegenContext(model)
- val bindingGen = RequestBindingGenerator(
- codegenContext,
- // Any protocol is fine for this test.
- RestJson(codegenContext),
- operationShape,
- )
- writer.rustBlock("impl PutObjectInput") {
- // RequestBindingGenerator's functions expect to be rendered inside a function,
- // but the unit test needs to call some of these functions individually. This generates
- // some wrappers that can be called directly from the tests. The functions will get duplicated,
- // but that's not a problem.
-
- rustBlock(
- "pub fn test_uri_query(&self, mut output: &mut String) -> Result<(), #T>",
- TestRuntimeConfig.operationBuildError(),
- ) {
- bindingGen.renderUpdateHttpBuilder(this)
- rust("uri_query(self, output)")
- }
-
- rustBlock(
- "pub fn test_uri_base(&self, mut output: &mut String) -> Result<(), #T>",
- TestRuntimeConfig.operationBuildError(),
- ) {
- bindingGen.renderUpdateHttpBuilder(this)
- rust("uri_base(self, output)")
- }
-
- rustBlock(
- "pub fn test_request_builder_base(&self) -> Result<#T, #T>",
- RuntimeType.HttpRequestBuilder,
- TestRuntimeConfig.operationBuildError(),
- ) {
- bindingGen.renderUpdateHttpBuilder(this)
- rust("let builder = #T::new();", RuntimeType.HttpRequestBuilder)
- rust("update_http_builder(self, builder)")
+ private fun renderOperation(rustCrate: RustCrate) {
+ inputShape.renderWithModelBuilder(model, symbolProvider, rustCrate)
+ rustCrate.withModule(ClientRustModule.Input) {
+ val codegenContext = testClientCodegenContext(model)
+ val bindingGen = RequestBindingGenerator(
+ codegenContext,
+ // Any protocol is fine for this test.
+ RestJson(codegenContext),
+ operationShape,
+ )
+ rustBlock("impl PutObjectInput") {
+ // RequestBindingGenerator's functions expect to be rendered inside a function,
+ // but the unit test needs to call some of these functions individually. This generates
+ // some wrappers that can be called directly from the tests. The functions will get duplicated,
+ // but that's not a problem.
+
+ rustBlock(
+ "pub fn test_uri_query(&self, mut output: &mut String) -> Result<(), #T>",
+ TestRuntimeConfig.operationBuildError(),
+ ) {
+ bindingGen.renderUpdateHttpBuilder(this)
+ rust("uri_query(self, output)")
+ }
+
+ rustBlock(
+ "pub fn test_uri_base(&self, mut output: &mut String) -> Result<(), #T>",
+ TestRuntimeConfig.operationBuildError(),
+ ) {
+ bindingGen.renderUpdateHttpBuilder(this)
+ rust("uri_base(self, output)")
+ }
+
+ rustBlock(
+ "pub fn test_request_builder_base(&self) -> Result<#T, #T>",
+ RuntimeType.HttpRequestBuilder,
+ TestRuntimeConfig.operationBuildError(),
+ ) {
+ bindingGen.renderUpdateHttpBuilder(this)
+ rust("let builder = #T::new();", RuntimeType.HttpRequestBuilder)
+ rust("update_http_builder(self, builder)")
+ }
}
}
}
@@ -179,9 +181,8 @@ class RequestBindingGeneratorTest {
@Test
fun `generates valid request bindings`() {
val project = TestWorkspace.testProject(symbolProvider)
- project.withModule(RustModule.public("input")) { // Currently rendering the operation renders the protocols—I want to separate that at some point.
- renderOperation(this)
-
+ renderOperation(project)
+ project.withModule(ClientRustModule.Input) { // Currently rendering the operation renders the protocols—I want to separate that at some point.
unitTest(
name = "generate_uris",
test = """
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGeneratorTest.kt
index 19ab23102fd..e6c2d1ae2a4 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/ResponseBindingGeneratorTest.kt
@@ -8,12 +8,11 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.http
import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
-import software.amazon.smithy.rust.codegen.client.testutil.testCodegenContext
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
-import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
+import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
+import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
+import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpLocation
import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpTraitHttpBindingResolver
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolContentTypes
@@ -67,23 +66,25 @@ class ResponseBindingGeneratorTest {
""".asSmithyModel()
private val model = OperationNormalizer.transform(baseModel)
private val operationShape = model.expectShape(ShapeId.from("smithy.example#PutObject"), OperationShape::class.java)
- private val codegenContext: CodegenContext = testCodegenContext(model)
+ private val codegenContext = testClientCodegenContext(model)
private val symbolProvider = codegenContext.symbolProvider
- private fun RustWriter.renderOperation() {
+ private fun RustCrate.renderOperation() {
operationShape.outputShape(model).renderWithModelBuilder(model, symbolProvider, this)
- rustBlock("impl PutObjectOutput") {
- val bindings = HttpTraitHttpBindingResolver(model, ProtocolContentTypes.consistent("dont-care"))
- .responseBindings(operationShape)
- .filter { it.location == HttpLocation.HEADER }
- bindings.forEach { binding ->
- val runtimeType = ResponseBindingGenerator(
- RestJson(codegenContext),
- codegenContext,
- operationShape,
- ).generateDeserializeHeaderFn(binding)
- // little hack to force these functions to be generated
- rust("// use #T;", runtimeType)
+ withModule(ClientRustModule.Output) {
+ rustBlock("impl PutObjectOutput") {
+ val bindings = HttpTraitHttpBindingResolver(model, ProtocolContentTypes.consistent("dont-care"))
+ .responseBindings(operationShape)
+ .filter { it.location == HttpLocation.HEADER }
+ bindings.forEach { binding ->
+ val runtimeType = ResponseBindingGenerator(
+ RestJson(codegenContext),
+ codegenContext,
+ operationShape,
+ ).generateDeserializeHeaderFn(binding)
+ // little hack to force these functions to be generated
+ rust("// use #T;", runtimeType)
+ }
}
}
}
@@ -91,8 +92,8 @@ class ResponseBindingGeneratorTest {
@Test
fun deserializeHeadersIntoOutputShape() {
val testProject = TestWorkspace.testProject(symbolProvider)
- testProject.withModule(RustModule.public("output")) {
- renderOperation()
+ testProject.renderOperation()
+ testProject.withModule(ClientRustModule.Output) {
unitTest(
"http_header_deser",
"""
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt
index 1da709422d0..c6f427d318e 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGeneratorTest.kt
@@ -21,7 +21,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolPayloadGenerator
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolTraitImplGenerator
@@ -62,10 +61,11 @@ private class TestProtocolTraitImplGenerator(
fn parse(&self, _response: {Response}<#{Bytes}>) -> Self::Output {
${operationWriter.escape(correctResponse)}
}
- }""",
+ }
+ """,
"parse_strict" to RuntimeType.parseStrictResponse(codegenContext.runtimeConfig),
"Output" to symbolProvider.toSymbol(operationShape.outputShape(codegenContext.model)),
- "Error" to operationShape.errorSymbol(symbolProvider),
+ "Error" to symbolProvider.symbolForOperationError(operationShape),
"Response" to RuntimeType.HttpResponse,
"Bytes" to RuntimeType.Bytes,
)
@@ -92,7 +92,7 @@ private class TestProtocolMakeOperationGenerator(
// A stubbed test protocol to do enable testing intentionally broken protocols
private class TestProtocolGenerator(
- codegenContext: CodegenContext,
+ codegenContext: ClientCodegenContext,
protocol: Protocol,
httpRequestBuilder: String,
body: String,
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt
new file mode 100644
index 00000000000..df33d83151d
--- /dev/null
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rust.codegen.client.smithy.protocols
+
+import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
+import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
+
+class AwsQueryCompatibleTest {
+ @Test
+ fun `aws-query-compatible json with aws query error should allow for retrieving error code and type from custom header`() {
+ val model = """
+ namespace test
+ use aws.protocols#awsJson1_0
+ use aws.protocols#awsQueryCompatible
+ use aws.protocols#awsQueryError
+
+ @awsQueryCompatible
+ @awsJson1_0
+ service TestService {
+ version: "2023-02-20",
+ operations: [SomeOperation]
+ }
+
+ operation SomeOperation {
+ input: SomeOperationInputOutput,
+ output: SomeOperationInputOutput,
+ errors: [InvalidThingException],
+ }
+
+ structure SomeOperationInputOutput {
+ a: String,
+ b: Integer
+ }
+
+ @awsQueryError(
+ code: "InvalidThing",
+ httpResponseCode: 400,
+ )
+ @error("client")
+ structure InvalidThingException {
+ message: String
+ }
+ """.asSmithyModel()
+
+ clientIntegrationTest(model) { clientCodegenContext, rustCrate ->
+ val moduleName = clientCodegenContext.moduleUseName()
+ rustCrate.integrationTest("should_parse_code_and_type_fields") {
+ rust(
+ """
+ ##[test]
+ fn should_parse_code_and_type_fields() {
+ use aws_smithy_http::response::ParseStrictResponse;
+
+ let response = http::Response::builder()
+ .header(
+ "x-amzn-query-error",
+ http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"),
+ )
+ .status(400)
+ .body(
+ r##"{
+ "__type": "com.amazonaws.sqs##QueueDoesNotExist",
+ "message": "Some user-visible message"
+ }"##,
+ )
+ .unwrap();
+ let some_operation = $moduleName::operation::SomeOperation::new();
+ let error = some_operation
+ .parse(&response.map(bytes::Bytes::from))
+ .err()
+ .unwrap();
+ assert_eq!(
+ Some("AWS.SimpleQueueService.NonExistentQueue"),
+ error.meta().code(),
+ );
+ assert_eq!(Some("Sender"), error.meta().extra("type"));
+ }
+ """,
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `aws-query-compatible json without aws query error should allow for retrieving error code from payload`() {
+ val model = """
+ namespace test
+ use aws.protocols#awsJson1_0
+ use aws.protocols#awsQueryCompatible
+
+ @awsQueryCompatible
+ @awsJson1_0
+ service TestService {
+ version: "2023-02-20",
+ operations: [SomeOperation]
+ }
+
+ operation SomeOperation {
+ input: SomeOperationInputOutput,
+ output: SomeOperationInputOutput,
+ errors: [InvalidThingException],
+ }
+
+ structure SomeOperationInputOutput {
+ a: String,
+ b: Integer
+ }
+
+ @error("client")
+ structure InvalidThingException {
+ message: String
+ }
+ """.asSmithyModel()
+
+ clientIntegrationTest(model) { clientCodegenContext, rustCrate ->
+ val moduleName = clientCodegenContext.moduleUseName()
+ rustCrate.integrationTest("should_parse_code_from_payload") {
+ rust(
+ """
+ ##[test]
+ fn should_parse_code_from_payload() {
+ use aws_smithy_http::response::ParseStrictResponse;
+
+ let response = http::Response::builder()
+ .status(400)
+ .body(
+ r##"{
+ "__type": "com.amazonaws.sqs##QueueDoesNotExist",
+ "message": "Some user-visible message"
+ }"##,
+ )
+ .unwrap();
+ let some_operation = $moduleName::operation::SomeOperation::new();
+ let error = some_operation
+ .parse(&response.map(bytes::Bytes::from))
+ .err()
+ .unwrap();
+ assert_eq!(Some("QueueDoesNotExist"), error.meta().code());
+ assert_eq!(None, error.meta().extra("type"));
+ }
+ """,
+ )
+ }
+ }
+ }
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt
deleted file mode 100644
index 1717dab2d67..00000000000
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamBaseRequirements.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package software.amazon.smithy.rust.codegen.client.smithy.protocols.eventstream
-
-import org.junit.jupiter.api.extension.ExtensionContext
-import org.junit.jupiter.params.provider.Arguments
-import org.junit.jupiter.params.provider.ArgumentsProvider
-import software.amazon.smithy.codegen.core.Symbol
-import software.amazon.smithy.model.Model
-import software.amazon.smithy.model.shapes.ServiceShape
-import software.amazon.smithy.model.shapes.ShapeId
-import software.amazon.smithy.model.shapes.StructureShape
-import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
-import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator
-import software.amazon.smithy.rust.codegen.client.testutil.clientTestRustSettings
-import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
-import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
-import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
-import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.error.OperationErrorGenerator
-import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock
-import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels
-import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestRequirements
-import java.util.stream.Stream
-
-class TestCasesProvider : ArgumentsProvider {
- override fun provideArguments(context: ExtensionContext?): Stream =
- EventStreamTestModels.TEST_CASES.map { Arguments.of(it) }.stream()
-}
-
-abstract class ClientEventStreamBaseRequirements : EventStreamTestRequirements {
- override fun createCodegenContext(
- model: Model,
- serviceShape: ServiceShape,
- protocolShapeId: ShapeId,
- codegenTarget: CodegenTarget,
- ): ClientCodegenContext = ClientCodegenContext(
- model,
- testSymbolProvider(model),
- serviceShape,
- protocolShapeId,
- clientTestRustSettings(),
- CombinedClientCodegenDecorator(emptyList()),
- )
-
- override fun renderBuilderForShape(
- writer: RustWriter,
- codegenContext: ClientCodegenContext,
- shape: StructureShape,
- ) {
- BuilderGenerator(codegenContext.model, codegenContext.symbolProvider, shape).apply {
- render(writer)
- writer.implBlock(shape, codegenContext.symbolProvider) {
- renderConvenienceMethod(writer)
- }
- }
- }
-
- override fun renderOperationError(
- writer: RustWriter,
- model: Model,
- symbolProvider: RustSymbolProvider,
- operationSymbol: Symbol,
- errors: List,
- ) {
- OperationErrorGenerator(model, symbolProvider, operationSymbol, errors).render(writer)
- }
-}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt
index 936d3b63243..349f6a8cf31 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamMarshallerGeneratorTest.kt
@@ -5,42 +5,30 @@
package software.amazon.smithy.rust.codegen.client.smithy.protocols.eventstream
+import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
import org.junit.jupiter.params.provider.ArgumentsSource
-import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
-import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.EventStreamMarshallerGenerator
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.testutil.EventStreamMarshallTestCases.writeMarshallTestCases
import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels
-import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestTools
-import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestVariety
-import software.amazon.smithy.rust.codegen.core.testutil.TestEventStreamProject
-import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
+import software.amazon.smithy.rust.codegen.core.testutil.testModule
+import java.util.stream.Stream
class ClientEventStreamMarshallerGeneratorTest {
@ParameterizedTest
@ArgumentsSource(TestCasesProvider::class)
fun test(testCase: EventStreamTestModels.TestCase) {
- EventStreamTestTools.runTestCase(
- testCase,
- object : ClientEventStreamBaseRequirements() {
- override fun renderGenerator(
- codegenContext: ClientCodegenContext,
- project: TestEventStreamProject,
- protocol: Protocol,
- ): RuntimeType = EventStreamMarshallerGenerator(
- project.model,
- CodegenTarget.CLIENT,
- TestRuntimeConfig,
- project.symbolProvider,
- project.streamShape,
- protocol.structuredDataSerializer(project.operationShape),
- testCase.requestContentType,
- ).render()
- },
- CodegenTarget.CLIENT,
- EventStreamTestVariety.Marshall,
- )
+ clientIntegrationTest(testCase.model) { _, rustCrate ->
+ rustCrate.testModule {
+ writeMarshallTestCases(testCase, optionalBuilderInputs = false)
+ }
+ }
}
}
+
+class TestCasesProvider : ArgumentsProvider {
+ override fun provideArguments(context: ExtensionContext?): Stream =
+ EventStreamTestModels.TEST_CASES.map { Arguments.of(it) }.stream()
+}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt
index f9be7b3bf4c..db44d62a61c 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/eventstream/ClientEventStreamUnmarshallerGeneratorTest.kt
@@ -7,43 +7,60 @@ package software.amazon.smithy.rust.codegen.client.smithy.protocols.eventstream
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ArgumentsSource
-import software.amazon.smithy.codegen.core.Symbol
-import software.amazon.smithy.model.shapes.StructureShape
-import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
-import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.generators.builderSymbol
-import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
-import software.amazon.smithy.rust.codegen.core.smithy.protocols.parse.EventStreamUnmarshallerGenerator
+import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestModels
-import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestTools
-import software.amazon.smithy.rust.codegen.core.testutil.EventStreamTestVariety
-import software.amazon.smithy.rust.codegen.core.testutil.TestEventStreamProject
+import software.amazon.smithy.rust.codegen.core.testutil.EventStreamUnmarshallTestCases.writeUnmarshallTestCases
+import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams
+import software.amazon.smithy.rust.codegen.core.testutil.testModule
+import software.amazon.smithy.rust.codegen.core.testutil.unitTest
class ClientEventStreamUnmarshallerGeneratorTest {
@ParameterizedTest
@ArgumentsSource(TestCasesProvider::class)
fun test(testCase: EventStreamTestModels.TestCase) {
- EventStreamTestTools.runTestCase(
- testCase,
- object : ClientEventStreamBaseRequirements() {
- override fun renderGenerator(
- codegenContext: ClientCodegenContext,
- project: TestEventStreamProject,
- protocol: Protocol,
- ): RuntimeType {
- fun builderSymbol(shape: StructureShape): Symbol = shape.builderSymbol(codegenContext.symbolProvider)
- return EventStreamUnmarshallerGenerator(
- protocol,
- codegenContext,
- project.operationShape,
- project.streamShape,
- ::builderSymbol,
- ).render()
- }
- },
- CodegenTarget.CLIENT,
- EventStreamTestVariety.Unmarshall,
- )
+ clientIntegrationTest(
+ testCase.model,
+ IntegrationTestParams(service = "test#TestService", addModuleToEventStreamAllowList = true),
+ ) { _, rustCrate ->
+ val generator = "crate::event_stream_serde::TestStreamUnmarshaller"
+
+ rustCrate.testModule {
+ rust("##![allow(unused_imports, dead_code)]")
+ writeUnmarshallTestCases(testCase, optionalBuilderInputs = false)
+
+ unitTest(
+ "unknown_message",
+ """
+ let message = msg("event", "NewUnmodeledMessageType", "application/octet-stream", b"hello, world!");
+ let result = $generator::new().unmarshall(&message);
+ assert!(result.is_ok(), "expected ok, got: {:?}", result);
+ assert!(expect_event(result.unwrap()).is_unknown());
+ """,
+ )
+
+ unitTest(
+ "generic_error",
+ """
+ let message = msg(
+ "exception",
+ "UnmodeledError",
+ "${testCase.responseContentType}",
+ br#"${testCase.validUnmodeledError}"#
+ );
+ let result = $generator::new().unmarshall(&message);
+ assert!(result.is_ok(), "expected ok, got: {:?}", result);
+ match expect_error(result.unwrap()) {
+ TestStreamError::Unhandled(err) => {
+ let message = format!("{}", aws_smithy_types::error::display::DisplayErrorContext(&err));
+ let expected = "message: \"unmodeled error\"";
+ assert!(message.contains(expected), "Expected '{message}' to contain '{expected}'");
+ }
+ kind => panic!("expected generic error, but got {:?}", kind),
+ }
+ """,
+ )
+ }
+ }
}
}
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/RemoveEventStreamOperationsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/RemoveEventStreamOperationsTest.kt
index 7873a74c2e1..4cc9c594aaa 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/RemoveEventStreamOperationsTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/transformers/RemoveEventStreamOperationsTest.kt
@@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenConfig
-import software.amazon.smithy.rust.codegen.client.testutil.clientTestRustSettings
+import software.amazon.smithy.rust.codegen.client.testutil.testClientRustSettings
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import java.util.Optional
@@ -49,7 +49,7 @@ internal class RemoveEventStreamOperationsTest {
fun `remove event stream ops from services that are not in the allow list`() {
val transformed = RemoveEventStreamOperations.transform(
model,
- clientTestRustSettings(
+ testClientRustSettings(
codegenConfig = ClientCodegenConfig(eventStreamAllowList = setOf("not-test-module")),
),
)
@@ -61,7 +61,7 @@ internal class RemoveEventStreamOperationsTest {
fun `keep event stream ops from services that are in the allow list`() {
val transformed = RemoveEventStreamOperations.transform(
model,
- clientTestRustSettings(
+ testClientRustSettings(
codegenConfig = ClientCodegenConfig(eventStreamAllowList = setOf("test-module")),
),
)
diff --git a/codegen-core/common-test-models/naming-obstacle-course-casing.smithy b/codegen-core/common-test-models/naming-obstacle-course-casing.smithy
new file mode 100644
index 00000000000..fb80a46d48b
--- /dev/null
+++ b/codegen-core/common-test-models/naming-obstacle-course-casing.smithy
@@ -0,0 +1,63 @@
+$version: "1.0"
+namespace casing
+
+use aws.protocols#awsJson1_1
+
+// TODO(https://github.com/awslabs/smithy-rs/issues/2340): The commented part of the model breaks the generator in a
+// miriad of ways. Any solution to the linked issue must address this.
+
+/// Confounds model generation machinery with lots of problematic casing
+@awsJson1_1
+service ACRONYMInside_Service {
+ operations: [
+ DoNothing,
+ // ACRONYMInside_Op
+ // ACRONYM_InsideOp
+ ]
+}
+
+operation DoNothing {}
+
+// operation ACRONYMInside_Op {
+// input: Input,
+// output: Output,
+// errors: [Error],
+// }
+
+// operation ACRONYM_InsideOp {
+// input: Input,
+// output: Output,
+// errors: [Error],
+// }
+
+// structure Input {
+// ACRONYMInside_Member: ACRONYMInside_Struct,
+// ACRONYM_Inside_Member: ACRONYM_InsideStruct,
+// ACRONYM_InsideMember: ACRONYMInsideStruct
+// }
+
+// structure Output {
+// ACRONYMInside_Member: ACRONYMInside_Struct,
+// ACRONYM_Inside_Member: ACRONYM_InsideStruct,
+// ACRONYM_InsideMember: ACRONYMInsideStruct
+// }
+
+// @error("client")
+// structure Error {
+// ACRONYMInside_Member: ACRONYMInside_Struct,
+// ACRONYM_Inside_Member: ACRONYM_InsideStruct,
+// ACRONYM_InsideMember: ACRONYMInsideStruct
+// }
+
+// structure ACRONYMInside_Struct {
+// ACRONYMInside_Member: ACRONYM_InsideStruct,
+// ACRONYM_Inside_Member: Integer,
+// }
+
+// structure ACRONYM_InsideStruct {
+// ACRONYMInside_Member: Integer,
+// }
+
+// structure ACRONYMInsideStruct {
+// ACRONYMInside_Member: Integer,
+// }
diff --git a/codegen-core/common-test-models/simple.smithy b/codegen-core/common-test-models/simple.smithy
index 43c4bc6aca0..c7e58c8e4a8 100644
--- a/codegen-core/common-test-models/simple.smithy
+++ b/codegen-core/common-test-models/simple.smithy
@@ -1,136 +1,22 @@
-$version: "1.0"
+$version: "2.0"
namespace com.amazonaws.simple
use aws.protocols#restJson1
-use smithy.test#httpRequestTests
-use smithy.test#httpResponseTests
-use smithy.framework#ValidationException
@restJson1
-@title("SimpleService")
-@documentation("A simple service example, with a Service resource that can be registered and a readonly healthcheck")
service SimpleService {
- version: "2022-01-01",
- resources: [
- Service,
- ],
operations: [
- Healthcheck,
- StoreServiceBlob,
- ],
+ Operation
+ ]
}
-@documentation("Id of the service that will be registered")
-string ServiceId
-
-@documentation("Name of the service that will be registered")
-string ServiceName
-
-@error("client")
-@documentation(
- """
- Returned when a new resource cannot be created because one already exists.
- """
-)
-structure ResourceAlreadyExists {
- @required
- message: String
-}
-
-@documentation("A resource that can register services")
-resource Service {
- identifiers: { id: ServiceId },
- put: RegisterService,
-}
-
-@idempotent
-@http(method: "PUT", uri: "/service/{id}")
-@documentation("Service register operation")
-@httpRequestTests([
- {
- id: "RegisterServiceRequestTest",
- protocol: "aws.protocols#restJson1",
- uri: "/service/1",
- headers: {
- "Content-Type": "application/json",
- },
- params: { id: "1", name: "TestService" },
- body: "{\"name\":\"TestService\"}",
- method: "PUT",
- }
-])
-@httpResponseTests([
- {
- id: "RegisterServiceResponseTest",
- protocol: "aws.protocols#restJson1",
- params: { id: "1", name: "TestService" },
- body: "{\"id\":\"1\",\"name\":\"TestService\"}",
- code: 200,
- headers: {
- "Content-Length": "31"
- }
- }
-])
-operation RegisterService {
- input: RegisterServiceInputRequest,
- output: RegisterServiceOutputResponse,
- errors: [ResourceAlreadyExists, ValidationException]
-}
-
-@documentation("Service register input structure")
-structure RegisterServiceInputRequest {
- @required
- @httpLabel
- id: ServiceId,
- name: ServiceName,
-}
-
-@documentation("Service register output structure")
-structure RegisterServiceOutputResponse {
- @required
- id: ServiceId,
- name: ServiceName,
-}
-
-@readonly
-@http(uri: "/healthcheck", method: "GET")
-@documentation("Read-only healthcheck operation")
-operation Healthcheck {
- input: HealthcheckInputRequest,
- output: HealthcheckOutputResponse
+@http(uri: "/operation", method: "POST")
+operation Operation {
+ input: OperationInputOutput
+ output: OperationInputOutput
}
-@documentation("Service healthcheck output structure")
-structure HealthcheckInputRequest {
-
-}
-
-@documentation("Service healthcheck input structure")
-structure HealthcheckOutputResponse {
-
-}
-
-@readonly
-@http(method: "POST", uri: "/service/{id}/blob")
-@documentation("Stores a blob for a service id")
-operation StoreServiceBlob {
- input: StoreServiceBlobInput,
- output: StoreServiceBlobOutput,
- errors: [ValidationException]
-}
-
-@documentation("Store a blob for a service id input structure")
-structure StoreServiceBlobInput {
- @required
- @httpLabel
- id: ServiceId,
- @required
- @httpPayload
- content: Blob,
-}
-
-@documentation("Store a blob for a service id output structure")
-structure StoreServiceBlobOutput {
-
+structure OperationInputOutput {
+ message: String
}
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt
index 6d355da68a9..167fd2997fa 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt
@@ -95,6 +95,13 @@ class InlineDependency(
CargoDependency.Http,
)
+ fun awsQueryCompatibleErrors(runtimeConfig: RuntimeConfig) =
+ forInlineableRustFile(
+ "aws_query_compatible_errors",
+ CargoDependency.smithyJson(runtimeConfig),
+ CargoDependency.Http,
+ )
+
fun idempotencyToken() =
forInlineableRustFile("idempotency_token", CargoDependency.FastRand)
@@ -133,6 +140,8 @@ data class CargoDependency(
return copy(features = features.toMutableSet().apply { add(feature) })
}
+ fun toDevDependency() = copy(scope = DependencyScope.Dev)
+
override fun version(): String = when (location) {
is CratesIo -> location.version
is Local -> "local"
@@ -220,7 +229,12 @@ data class CargoDependency(
val Smol: CargoDependency = CargoDependency("smol", CratesIo("1.2.0"), DependencyScope.Dev)
val TempFile: CargoDependency = CargoDependency("tempfile", CratesIo("3.2.0"), DependencyScope.Dev)
val Tokio: CargoDependency =
- CargoDependency("tokio", CratesIo("1.8.4"), DependencyScope.Dev, features = setOf("macros", "test-util", "rt-multi-thread"))
+ CargoDependency(
+ "tokio",
+ CratesIo("1.8.4"),
+ DependencyScope.Dev,
+ features = setOf("macros", "test-util", "rt-multi-thread"),
+ )
val TracingAppender: CargoDependency = CargoDependency(
"tracing-appender",
CratesIo("0.2.2"),
@@ -236,12 +250,17 @@ data class CargoDependency(
fun smithyAsync(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-async")
fun smithyChecksums(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-checksums")
fun smithyClient(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-client")
+ fun smithyClientTestUtil(runtimeConfig: RuntimeConfig) =
+ smithyClient(runtimeConfig).toDevDependency().withFeature("test-util")
+
fun smithyEventStream(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-eventstream")
fun smithyHttp(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-http")
+ fun smithyHttpAuth(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-http-auth")
fun smithyHttpTower(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-http-tower")
fun smithyJson(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-json")
fun smithyProtocolTestHelpers(runtimeConfig: RuntimeConfig) =
runtimeConfig.smithyRuntimeCrate("smithy-protocol-test", scope = DependencyScope.Dev)
+
fun smithyQuery(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-query")
fun smithyTypes(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-types")
fun smithyXml(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-xml")
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt
index 6745e3b2ee7..ef2786a396d 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt
@@ -32,7 +32,10 @@ sealed class RustModule {
val documentation: String? = null,
val parent: RustModule = LibRs,
val inline: Boolean = false,
+ /* module is a cfg(test) module */
+ val tests: Boolean = false,
) : RustModule() {
+
init {
check(!name.contains("::")) {
"Module names CANNOT contain `::`—modules must be nested with parent (name was: `$name`)"
@@ -45,6 +48,12 @@ sealed class RustModule {
"Module `$name` cannot be a module name—it is a reserved word."
}
}
+
+ /** Convert a module into a module gated with `#[cfg(test)]` */
+ fun cfgTest(): LeafModule = this.copy(
+ rustMetadata = rustMetadata.copy(additionalAttributes = rustMetadata.additionalAttributes + Attribute.CfgTest),
+ tests = true,
+ )
}
companion object {
@@ -78,13 +87,17 @@ sealed class RustModule {
fun pubCrate(name: String, documentation: String? = null, parent: RustModule): LeafModule =
new(name, visibility = Visibility.PUBCRATE, documentation = documentation, inline = false, parent = parent)
- /* Common modules used across client, server and tests */
- val Config = public("config", documentation = "Configuration for the service.")
- val Error = public("error", documentation = "All error types that operations can return. Documentation on these types is copied from the model.")
- val Model = public("model", documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.")
- val Input = public("input", documentation = "Input structures for operations. Documentation on these types is copied from the model.")
- val Output = public("output", documentation = "Output structures for operations. Documentation on these types is copied from the model.")
- val Types = public("types", documentation = "Data primitives referenced by other data types.")
+ fun inlineTests(
+ name: String = "test",
+ parent: RustModule = LibRs,
+ additionalAttributes: List